diff --git a/initializr-actuator/pom.xml b/initializr-actuator/pom.xml index 24b57e9b..69f7062e 100644 --- a/initializr-actuator/pom.xml +++ b/initializr-actuator/pom.xml @@ -44,6 +44,11 @@ test-jar test + + org.rauschig + jarchivelib + test + io.spring.initializr initializr-web @@ -82,13 +87,4 @@ - - - - org.codehaus.gmavenplus - gmavenplus-plugin - - - - diff --git a/initializr-actuator/src/test/java/io/spring/initializr/actuate/ActuatorIntegrationTests.java b/initializr-actuator/src/test/java/io/spring/initializr/actuate/ActuatorIntegrationTests.java index 4b8d63dc..e2fe60c1 100644 --- a/initializr-actuator/src/test/java/io/spring/initializr/actuate/ActuatorIntegrationTests.java +++ b/initializr-actuator/src/test/java/io/spring/initializr/actuate/ActuatorIntegrationTests.java @@ -19,13 +19,11 @@ package io.spring.initializr.actuate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.util.Map; - +import org.json.JSONObject; import org.junit.Test; import org.springframework.test.context.ActiveProfiles; -import groovy.json.JsonSlurper; -import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests; /** * Tests for actuator specific features. @@ -34,9 +32,7 @@ import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; */ @ActiveProfiles("test-default") public class ActuatorIntegrationTests - extends AbstractInitializrControllerIntegrationTests { - - private final JsonSlurper slurper = new JsonSlurper(); + extends AbstractFullStackInitializrIntegrationTests { @Test public void infoHasExternalProperties() { @@ -49,36 +45,35 @@ public class ActuatorIntegrationTests @Test public void metricsAvailableByDefault() { downloadZip("/starter.zip?packaging=jar&javaVersion=1.8&style=web&style=jpa"); - Map result = metricsEndpoint(); - Integer requests = result.get("counter.initializr.requests"); - Integer packaging = result.get("counter.initializr.packaging.jar"); - Integer javaVersion = result.get("counter.initializr.java_version.1_8"); - Integer webDependency = result.get("counter.initializr.dependency.web"); - Integer jpaDependency = result.get("counter.initializr.dependency.jpa"); + JSONObject result = metricsEndpoint(); + int requests = result.getInt("counter.initializr.requests"); + int packaging = result.getInt("counter.initializr.packaging.jar"); + int javaVersion = result.getInt("counter.initializr.java_version.1_8"); + int webDependency = result.getInt("counter.initializr.dependency.web"); + int jpaDependency = result.getInt("counter.initializr.dependency.data-jpa"); // No jpa dep this time downloadZip("/starter.zip?packaging=jar&javaVersion=1.8&style=web"); - Map updatedResult = metricsEndpoint(); + JSONObject updatedResult = metricsEndpoint(); assertEquals("Number of request should have increased", requests + 1, - updatedResult.get("counter.initializr.requests").intValue()); + updatedResult.getInt("counter.initializr.requests")); assertEquals("jar packaging metric should have increased", packaging + 1, - updatedResult.get("counter.initializr.packaging.jar").intValue()); + updatedResult.getInt("counter.initializr.packaging.jar")); assertEquals("java version metric should have increased", javaVersion + 1, - updatedResult.get("counter.initializr.java_version.1_8").intValue()); + updatedResult.getInt("counter.initializr.java_version.1_8")); assertEquals("web dependency metric should have increased", webDependency + 1, - updatedResult.get("counter.initializr.dependency.web").intValue()); - assertEquals("jpa dependency metric should not have increased", jpaDependency, - updatedResult.get("counter.initializr.dependency.jpa")); + updatedResult.getInt("counter.initializr.dependency.web")); + assertEquals("jpa dependency metric should not have increased", jpaDependency + 0, + updatedResult.getInt("counter.initializr.dependency.data-jpa")); } - private Map metricsEndpoint() { + private JSONObject metricsEndpoint() { return parseJson(getRestTemplate().getForObject(createUrl("/metrics"), String.class)); } - @SuppressWarnings("unchecked") - private Map parseJson(String content) { - return (Map) slurper.parseText(content); + private JSONObject parseJson(String content) { + return new JSONObject(content); } } diff --git a/initializr-actuator/src/test/java/io/spring/initializr/actuate/stat/MainControllerStatsIntegrationTests.java b/initializr-actuator/src/test/java/io/spring/initializr/actuate/stat/MainControllerStatsIntegrationTests.java index ce6097d3..503245cc 100644 --- a/initializr-actuator/src/test/java/io/spring/initializr/actuate/stat/MainControllerStatsIntegrationTests.java +++ b/initializr-actuator/src/test/java/io/spring/initializr/actuate/stat/MainControllerStatsIntegrationTests.java @@ -25,8 +25,9 @@ import static org.junit.Assert.fail; import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.Map; +import org.json.JSONArray; +import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -41,7 +42,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException; -import groovy.json.JsonSlurper; import io.spring.initializr.actuate.stat.MainControllerStatsIntegrationTests.StatsMockController; import io.spring.initializr.actuate.stat.MainControllerStatsIntegrationTests.StatsMockController.Content; import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests; @@ -62,8 +62,6 @@ public class MainControllerStatsIntegrationTests @Autowired private StatsProperties statsProperties; - private final JsonSlurper slurper = new JsonSlurper(); - @Before public void setup() { this.statsMockController.stats.clear(); @@ -77,13 +75,11 @@ public class MainControllerStatsIntegrationTests assertEquals("No stat got generated", 1, statsMockController.stats.size()); Content content = statsMockController.stats.get(0); - @SuppressWarnings("unchecked") - Map json = (Map) slurper.parseText(content.json); + JSONObject json = new JSONObject(content.json); assertEquals("com.foo", json.get("groupId")); assertEquals("bar", json.get("artifactId")); - @SuppressWarnings("unchecked") - List list = (List) json.get("dependencies"); - assertEquals(1, list.size()); + JSONArray list = json.getJSONArray("dependencies"); + assertEquals(1, list.length()); assertEquals("web", list.get(0)); } @@ -108,9 +104,8 @@ public class MainControllerStatsIntegrationTests assertEquals("No stat got generated", 1, statsMockController.stats.size()); Content content = statsMockController.stats.get(0); - @SuppressWarnings("unchecked") - Map json = (Map) slurper.parseText(content.json); - assertFalse("requestIp property should not be set", json.containsKey("requestIp")); + JSONObject json = new JSONObject(content.json); + assertFalse("requestIp property should not be set", json.has("requestIp")); } @Test @@ -121,8 +116,7 @@ public class MainControllerStatsIntegrationTests assertEquals("No stat got generated", 1, statsMockController.stats.size()); Content content = statsMockController.stats.get(0); - @SuppressWarnings("unchecked") - Map json = (Map) slurper.parseText(content.json); + JSONObject json = new JSONObject(content.json); assertEquals("Wrong requestIp", "10.0.0.123", json.get("requestIp")); } @@ -134,10 +128,9 @@ public class MainControllerStatsIntegrationTests assertEquals("No stat got generated", 1, statsMockController.stats.size()); Content content = statsMockController.stats.get(0); - @SuppressWarnings("unchecked") - Map json = (Map) slurper.parseText(content.json); + JSONObject json = new JSONObject(content.json); assertFalse("requestIpv4 property should not be set if value is not a valid IPv4", - json.containsKey("requestIpv4")); + json.has("requestIpv4")); } @Test @@ -148,10 +141,9 @@ public class MainControllerStatsIntegrationTests assertEquals("No stat got generated", 1, statsMockController.stats.size()); Content content = statsMockController.stats.get(0); - @SuppressWarnings("unchecked") - Map json = (Map) slurper.parseText(content.json); + JSONObject json = new JSONObject(content.json); assertFalse("requestCountry property should not be set if value is set to xx", - json.containsKey("requestCountry")); + json.has("requestCountry")); } @Test @@ -165,8 +157,7 @@ public class MainControllerStatsIntegrationTests assertEquals("No stat got generated", 1, statsMockController.stats.size()); Content content = statsMockController.stats.get(0); - @SuppressWarnings("unchecked") - Map json = (Map) slurper.parseText(content.json); + JSONObject json = new JSONObject(content.json); assertEquals("com.example", json.get("groupId")); assertEquals("demo", json.get("artifactId")); assertEquals(true, json.get("invalid")); diff --git a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/BillOfMaterials.groovy b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/BillOfMaterials.groovy index b9f9495b..cba01d40 100644 --- a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/BillOfMaterials.groovy +++ b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/BillOfMaterials.groovy @@ -70,10 +70,6 @@ class BillOfMaterials { final List mappings = [] - static BillOfMaterials create(String groupId, String artifactId, String version) { - new BillOfMaterials(groupId: groupId, artifactId: artifactId, version: version) - } - /** * Determine the version placeholder to use for this instance. If a version * property is defined, this returns the reference for the property. Otherwise @@ -135,14 +131,26 @@ class BillOfMaterials { private VersionRange range - static Mapping create(String versionRange, String version) { - new Mapping(versionRange: versionRange, version: version) - } - String determineVersionRangeRequirement() { range.toString() } + public static Mapping create(String range, String version) { + return new Mapping(versionRange: range, version: version); + } + + public static Mapping create(String range, String version, String... repositories) { + return new Mapping(versionRange: range, version: version, repositories: repositories); + } + + } + + public static BillOfMaterials create(String groupId, String artifactId) { + return new BillOfMaterials(groupId: groupId, artifactId: artifactId); + } + + public static BillOfMaterials create(String groupId, String artifactId, String version) { + return new BillOfMaterials(groupId: groupId, artifactId: artifactId, version: version); } } diff --git a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy index 9efe1027..9e62249b 100644 --- a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy +++ b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonInclude import groovy.transform.AutoClone import groovy.transform.AutoCloneStyle import groovy.transform.ToString +import io.spring.initializr.metadata.Dependency.Mapping; import io.spring.initializr.util.InvalidVersionException import io.spring.initializr.util.Version import io.spring.initializr.util.VersionParser @@ -35,19 +36,19 @@ import io.spring.initializr.util.VersionRange @ToString(ignoreNulls = true, includePackage = false) @JsonInclude(JsonInclude.Include.NON_EMPTY) @AutoClone(style = AutoCloneStyle.COPY_CONSTRUCTOR) -class Dependency extends MetadataElement { +class Dependency extends MetadataElement implements Describable { static final String SCOPE_COMPILE = 'compile' static final String SCOPE_COMPILE_ONLY = 'compileOnly' static final String SCOPE_RUNTIME = 'runtime' static final String SCOPE_PROVIDED = 'provided' static final String SCOPE_TEST = 'test' static final List SCOPE_ALL = [ - SCOPE_COMPILE, - SCOPE_RUNTIME, - SCOPE_COMPILE_ONLY, - SCOPE_PROVIDED, - SCOPE_TEST + SCOPE_COMPILE, + SCOPE_RUNTIME, + SCOPE_COMPILE_ONLY, + SCOPE_PROVIDED, + SCOPE_TEST ] List aliases = [] @@ -144,7 +145,7 @@ class Dependency extends MetadataElement { if (id == null) { if (!hasCoordinates()) { throw new InvalidInitializrMetadataException( - 'Invalid dependency, should have at least an id or a groupId/artifactId pair.') + 'Invalid dependency, should have at least an id or a groupId/artifactId pair.') } generateId() } else if (!hasCoordinates()) { @@ -160,7 +161,7 @@ class Dependency extends MetadataElement { } } else { throw new InvalidInitializrMetadataException( - "Invalid dependency, id should have the form groupId:artifactId[:version] but got $id") + "Invalid dependency, id should have the form groupId:artifactId[:version] but got $id") } } links.forEach { l -> @@ -176,7 +177,7 @@ class Dependency extends MetadataElement { versionRequirement = range.toString() } catch (InvalidVersionException ex) { throw new InvalidInitializrMetadataException("Invalid version range '$versionRange' for " + - "dependency with id '$id'", ex) + "dependency with id '$id'", ex) } } mappings.each { @@ -223,7 +224,7 @@ class Dependency extends MetadataElement { def generateId() { if (groupId == null || artifactId == null) { throw new IllegalArgumentException( - "Could not generate id for $this: at least groupId and artifactId must be set.") + "Could not generate id for $this: at least groupId and artifactId must be set.") } StringBuilder sb = new StringBuilder() sb.append(groupId).append(':').append(artifactId) @@ -257,6 +258,22 @@ class Dependency extends MetadataElement { private VersionRange range + public static Mapping create(String range, String groupId, String artifactId, + String version) { + new Mapping(versionRange: range, groupId: groupId, artifactId: artifactId, version: version); + } + } + + public static Dependency create(String groupId, String artifactId, String version, String scope) { + return new Dependency(groupId: groupId, artifactId: artifactId, version: version, scope: scope); + } + + public static Dependency withId(String id, String groupId, String artifactId, String version) { + return new Dependency(groupId: groupId, artifactId: artifactId, versionRange: version, id: id); + } + + public static Dependency withId(String id, String groupId, String artifactId) { + return new Dependency(groupId: groupId, artifactId: artifactId, id: id); } } diff --git a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Describable.groovy b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Describable.groovy new file mode 100644 index 00000000..ea0a89e2 --- /dev/null +++ b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Describable.groovy @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2017 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.metadata; + +interface Describable { + String getDescription(); +} diff --git a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Link.groovy b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Link.groovy index ed9ebdc1..95ad317a 100644 --- a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Link.groovy +++ b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Link.groovy @@ -104,4 +104,16 @@ class Link { new URI(result) } + public static Link create(String rel, String href) { + return new Link(rel: rel, href: href); + } + + public static Link create(String rel, String href, String description) { + return new Link(rel: rel, href: href, description: description); + } + + public static Link create(String rel, String href, boolean templated) { + return new Link(rel: rel, href: href, templated: templated); + } + } diff --git a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/ServiceCapability.groovy b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/ServiceCapability.groovy index 970f144d..b70d6e36 100644 --- a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/ServiceCapability.groovy +++ b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/ServiceCapability.groovy @@ -32,7 +32,7 @@ import org.springframework.util.Assert @JsonIgnoreProperties(["default", "all"]) @JsonInclude(JsonInclude.Include.NON_NULL) @AutoClone(style = AutoCloneStyle.COPY_CONSTRUCTOR) -abstract class ServiceCapability { +abstract class ServiceCapability implements Cloneable { final String id diff --git a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Type.groovy b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Type.groovy index 2d9e4e05..12431b29 100644 --- a/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Type.groovy +++ b/initializr-generator/src/main/groovy/io/spring/initializr/metadata/Type.groovy @@ -22,7 +22,7 @@ package io.spring.initializr.metadata * * @author Stephane Nicoll */ -class Type extends DefaultMetadataElement { +class Type extends DefaultMetadataElement implements Describable { String description diff --git a/initializr-web/pom.xml b/initializr-web/pom.xml index afee91b8..b0f45212 100644 --- a/initializr-web/pom.xml +++ b/initializr-web/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 io.spring.initializr @@ -35,19 +36,18 @@ com.github.ben-manes.caffeine caffeine + + org.rauschig + jarchivelib + + + org.json + json + - org.codehaus.groovy - groovy - provided - - - org.codehaus.groovy - groovy-ant - - - org.codehaus.groovy - groovy-json + org.springframework.boot + spring-boot-starter-mustache @@ -92,11 +92,6 @@ jsonassert test - - org.gebish - geb-core - test - org.seleniumhq.selenium selenium-firefox-driver @@ -143,10 +138,6 @@ - - org.codehaus.gmavenplus - gmavenplus-plugin - diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.groovy deleted file mode 100644 index 624c431d..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.groovy +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2012-2017 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.autoconfigure - -import org.springframework.web.servlet.resource.ResourceUrlProvider - -import java.util.concurrent.TimeUnit - -import com.github.benmanes.caffeine.cache.Caffeine -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.metadata.DependencyMetadataProvider -import io.spring.initializr.metadata.InitializrMetadataBuilder -import io.spring.initializr.metadata.InitializrMetadataProvider -import io.spring.initializr.metadata.InitializrProperties -import io.spring.initializr.util.GroovyTemplate -import io.spring.initializr.web.project.MainController -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.annotation.Autowired -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean -import org.springframework.boot.bind.RelaxedPropertyResolver -import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.cache.Cache -import org.springframework.cache.CacheManager -import org.springframework.cache.annotation.EnableCaching -import org.springframework.cache.caffeine.CaffeineCache -import org.springframework.cache.concurrent.ConcurrentMapCache -import org.springframework.cache.support.SimpleCacheManager -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.core.env.Environment -import org.springframework.web.client.RestTemplate - -/** - * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration - * Auto-configuration} to configure Spring initializr. In a web environment, - * configures the necessary controller to serve the applications from the - * root context. - * - *

Project generation can be customized by defining a custom - * {@link ProjectGenerator}. - * - * @author Stephane Nicoll - */ -@Configuration -@EnableCaching -@EnableConfigurationProperties(InitializrProperties) -class InitializrAutoConfiguration { - - @Autowired(required = false) - List postProcessors = [] - - @Bean - WebConfig webConfig() { - new WebConfig() - } - - @Bean - @ConditionalOnMissingBean - MainController initializrMainController(InitializrMetadataProvider metadataProvider, - GroovyTemplate groovyTemplate, - ResourceUrlProvider resourceUrlProvider, - ProjectGenerator projectGenerator, - DependencyMetadataProvider dependencyMetadataProvider) { - new MainController(metadataProvider, groovyTemplate, resourceUrlProvider - , projectGenerator, dependencyMetadataProvider) - } - - @Bean - @ConditionalOnMissingBean - UiController initializrUiController() { - new UiController() - } - - @Bean - @ConditionalOnMissingBean - ProjectGenerator projectGenerator() { - new ProjectGenerator() - } - - @Bean - @ConditionalOnMissingBean - GroovyTemplate groovyTemplate(Environment environment) { - def resolver = new RelaxedPropertyResolver(environment, 'spring.groovy.template.') - boolean cache = resolver.getProperty('cache', Boolean.class, true) - def groovyTemplate = new GroovyTemplate() - groovyTemplate.cache = cache - groovyTemplate - } - - @Bean - @ConditionalOnMissingBean - ProjectRequestResolver projectRequestResolver() { - new ProjectRequestResolver(postProcessors) - } - - @Bean - ProjectResourceLocator projectResourceLocator() { - return new ProjectResourceLocator() - } - - @Bean - @ConditionalOnMissingBean(InitializrMetadataProvider) - InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties) { - def metadata = InitializrMetadataBuilder.fromInitializrProperties(properties).build() - new DefaultInitializrMetadataProvider(metadata, new RestTemplate()) - } - - @Bean - @ConditionalOnMissingBean - DependencyMetadataProvider dependencyMetadataProvider() { - new DefaultDependencyMetadataProvider() - } - - @Bean - @ConditionalOnMissingBean - CacheManager cacheManager() { - def cacheManager = new SimpleCacheManager() - cacheManager.caches = Arrays.asList( - createConcurrentMapCache(600, 'initializr'), - new ConcurrentMapCache('dependency-metadata'), - new ConcurrentMapCache("project-resources")) - cacheManager - } - - private static Cache createConcurrentMapCache(Long timeToLive, String name) { - new CaffeineCache(name, Caffeine - .newBuilder() - .expireAfterWrite(timeToLive, TimeUnit.SECONDS) - .build()) - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/DependencyMetadataV21JsonMapper.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/DependencyMetadataV21JsonMapper.groovy deleted file mode 100644 index afc0b09d..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/DependencyMetadataV21JsonMapper.groovy +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2012-2017 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.mapper - -import groovy.json.JsonBuilder -import io.spring.initializr.metadata.BillOfMaterials -import io.spring.initializr.metadata.Dependency -import io.spring.initializr.metadata.DependencyMetadata -import io.spring.initializr.metadata.Repository - -/** - * A {@link DependencyMetadataJsonMapper} handling the metadata format for v2.1. - * - * @author Stephane Nicoll - */ -class DependencyMetadataV21JsonMapper implements DependencyMetadataJsonMapper { - - @Override - String write(DependencyMetadata metadata) { - JsonBuilder json = new JsonBuilder() - json { - bootVersion metadata.bootVersion.toString() - dependencies metadata.dependencies.collectEntries { id, d -> - [id, mapDependency(d)] - } - repositories metadata.repositories.collectEntries { id, r -> [id, mapRepository(r)] } - boms metadata.boms.collectEntries { id, b -> [id, mapBom(b)] } - } - json.toString() - } - - private static mapDependency(Dependency dep) { - def result = [:] - result.groupId = dep.groupId - result.artifactId = dep.artifactId - if (dep.version) { - result.version = dep.version - } - result.scope = dep.scope - if (dep.bom) { - result.bom = dep.bom - } - if (dep.repository) { - result.repository = dep.repository - } - result - } - - private static mapRepository(Repository repo) { - def result = [:] - result.name = repo.name - result.url = repo.url - result.snapshotEnabled = repo.snapshotsEnabled - result - } - - private static mapBom(BillOfMaterials bom) { - def result = [:] - result.groupId = bom.groupId - result.artifactId = bom.artifactId - if (bom.version) { - result.version = bom.version - } - if (bom.repositories) { - result.repositories = bom.repositories - } - result - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.groovy deleted file mode 100644 index fa40d9b8..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.groovy +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2012-2017 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.mapper - -import org.springframework.hateoas.TemplateVariable -import org.springframework.hateoas.TemplateVariables -import org.springframework.hateoas.UriTemplate - -/** - * A {@link InitializrMetadataJsonMapper} handling the metadata format for v2.1 - *

- * Version 2.1 brings the 'versionRange' attribute for a dependency to restrict - * the Spring Boot versions that can be used against it. That version also adds - * an additional `dependencies` endpoint. - * - * @author Stephane Nicoll - */ -class InitializrMetadataV21JsonMapper extends InitializrMetadataV2JsonMapper { - - private final TemplateVariables dependenciesVariables - - InitializrMetadataV21JsonMapper() { - this.dependenciesVariables = new TemplateVariables( - new TemplateVariable('bootVersion', TemplateVariable.VariableType.REQUEST_PARAM) - ) - } - - @Override - protected links(parent, types, appUrl) { - def links = super.links(parent, types, appUrl) - links['dependencies'] = dependenciesLink(appUrl) - links - } - - @Override - protected mapDependency(dependency) { - def content = mapValue(dependency) - if (dependency.versionRange) { - content['versionRange'] = dependency.versionRange - } - if (dependency.links) { - content._links = LinkMapper.mapLinks(dependency.links) - } - content - } - - private dependenciesLink(appUrl) { - String uri = appUrl != null ? appUrl + '/dependencies' : '/dependencies' - UriTemplate uriTemplate = new UriTemplate(uri, this.dependenciesVariables) - def result = [:] - result.href = uriTemplate.toString() - result.templated = true - result - } -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.groovy deleted file mode 100644 index 8830d03f..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.groovy +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2012-2017 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.mapper - -import groovy.json.JsonBuilder -import io.spring.initializr.metadata.InitializrMetadata -import io.spring.initializr.metadata.SingleSelectCapability - -import org.springframework.hateoas.TemplateVariable -import org.springframework.hateoas.TemplateVariables -import org.springframework.hateoas.UriTemplate - -/** - * A {@link InitializrMetadataJsonMapper} handling the metadata format for v2. - * - * @author Stephane Nicoll - */ -class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMapper { - - private final TemplateVariables templateVariables - - InitializrMetadataV2JsonMapper() { - this.templateVariables = new TemplateVariables( - new TemplateVariable('dependencies', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('packaging', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('javaVersion', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('language', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('bootVersion', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('groupId', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('artifactId', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('version', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('name', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('description', TemplateVariable.VariableType.REQUEST_PARAM), - new TemplateVariable('packageName', TemplateVariable.VariableType.REQUEST_PARAM) - ) - } - - @Override - String write(InitializrMetadata metadata, String appUrl) { - JsonBuilder json = new JsonBuilder() - json { - links(delegate, metadata.types.content, appUrl) - dependencies(delegate, metadata.dependencies) - type(delegate, metadata.types) - singleSelect(delegate, metadata.packagings) - singleSelect(delegate, metadata.javaVersions) - singleSelect(delegate, metadata.languages) - singleSelect(delegate, metadata.bootVersions) - text(delegate, metadata.groupId) - text(delegate, metadata.artifactId) - text(delegate, metadata.version) - text(delegate, metadata.name) - text(delegate, metadata.description) - text(delegate, metadata.packageName) - } - json.toString() - } - - protected links(parent, types, appUrl) { - def content = [:] - types.each { - content[it.id] = link(appUrl, it) - } - parent._links content - } - - protected link(appUrl, type) { - def result = [:] - result.href = generateTemplatedUri(appUrl, type) - result.templated = true - result - } - - private generateTemplatedUri(appUrl, type) { - String uri = appUrl != null ? appUrl + type.action : type.action - uri += "?type=$type.id" - UriTemplate uriTemplate = new UriTemplate(uri, this.templateVariables) - uriTemplate.toString() - } - - - protected dependencies(parent, capability) { - parent."$capability.id" { - type "$capability.type.name" - values capability.content.collect { - mapDependencyGroup(it) - } - } - } - - protected type(parent, capability) { - parent.type { - type 'action' - def defaultType = capability.default - if (defaultType) { - 'default' defaultType.id - } - values capability.content.collect { - mapType(it) - } - } - } - - protected singleSelect(parent, SingleSelectCapability capability) { - parent."$capability.id" { - type "$capability.type.name" - def defaultValue = capability.default - if (defaultValue) { - 'default' defaultValue.id - } - values capability.content.collect { - mapValue(it) - } - } - } - - protected text(parent, capability) { - parent."$capability.id" { - type "$capability.type.name" - def defaultValue = capability.content - if (defaultValue) { - 'default' defaultValue - } - } - } - - protected mapDependencyGroup(group) { - def result = [:] - result.name = group.name - if (group.hasProperty('description') && group.description) { - result.description = group.description - } - def items = [] - group.content.collect { - def dependency = mapDependency(it) - if (dependency) { - items << dependency - } - } - result.values = items - result - } - - protected mapDependency(dependency) { - if (!dependency.versionRange) { // only map the dependency if no versionRange is set - mapValue(dependency) - } - } - - protected mapType(type) { - def result = mapValue(type) - result.action = type.action - result.tags = type.tags - result - } - - protected mapValue(value) { - def result = [:] - result.id = value.id - result.name = value.name - if (value.hasProperty('description') && value.description) { - result.description = value.description - } - result - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/LinkMapper.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/LinkMapper.groovy deleted file mode 100644 index 535690fd..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/LinkMapper.groovy +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2012-2017 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.mapper - -import io.spring.initializr.metadata.Link - -/** - * Generate a json representation for {@link Link} - * - * @author Stephane Nicoll - */ -class LinkMapper { - - /** - * Map the specified links to a json model. If several links share - * the same relation, they are grouped together. - * @param links the links to map - * @return a model for the specified links - */ - static mapLinks(List links) { - def result = [:] - Map> byRel = new LinkedHashMap<>() - links.each { - def relLinks = byRel[it.rel] - if (!relLinks) { - relLinks = [] - byRel[it.rel] = relLinks - } - relLinks.add(it) - } - byRel.forEach { rel, l -> - if (l.size() == 1) { - def root = [:] - mapLink(l[0], root) - result[rel] = root - } else { - def root = [] - l.each { - def model = [:] - mapLink(it, model) - root << model - } - result[rel] = root - } - } - result - } - - private static mapLink(Link link, def model) { - model.href = link.href - if (link.templated) { - model.templated = true - } - if (link.description) { - model.title = link.description - } - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/project/AbstractInitializrController.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/project/AbstractInitializrController.groovy deleted file mode 100644 index cc7d4e95..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/project/AbstractInitializrController.groovy +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2012-2017 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 org.springframework.web.servlet.resource.ResourceUrlProvider - -import javax.servlet.http.HttpServletResponse - -import io.spring.initializr.generator.InvalidProjectRequestException -import io.spring.initializr.metadata.InitializrMetadataProvider -import io.spring.initializr.util.GroovyTemplate - -import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.ExceptionHandler -import org.springframework.web.servlet.support.ServletUriComponentsBuilder - -/** - * A base controller that uses a {@link InitializrMetadataProvider} - * - * @author Stephane Nicoll - */ -abstract class AbstractInitializrController { - - protected final InitializrMetadataProvider metadataProvider - private final GroovyTemplate groovyTemplate - private final Closure linkTo - private Boolean forceSsl - - protected AbstractInitializrController(InitializrMetadataProvider metadataProvider, - ResourceUrlProvider resourceUrlProvider, - GroovyTemplate groovyTemplate) { - this.metadataProvider = metadataProvider - this.groovyTemplate = groovyTemplate - this.linkTo = { link -> resourceUrlProvider.getForLookupPath(link)?:link } - } - - boolean isForceSsl() { - if (this.forceSsl == null) { - this.forceSsl = metadataProvider.get().configuration.env.forceSsl - } - return this.forceSsl; - - } - - @ExceptionHandler - public void invalidProjectRequest(HttpServletResponse response, InvalidProjectRequestException ex) { - response.sendError(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); - } - - /** - * Render the home page with the specified template. - */ - protected String renderHome(String templatePath) { - def metadata = metadataProvider.get() - - def model = [:] - model['serviceUrl'] = generateAppUrl() - metadata.properties.each { - if (it.key.equals('types')) { - model['types'] = it.value.clone() - } else { - model[it.key] = it.value - } - } - - // Only keep project type - model['types'].content.removeAll { t -> !'project'.equals(t.tags['format']) } - - // Google analytics support - model['trackingCode'] = metadata.configuration.env.googleAnalyticsTrackingCode - - // Linking to static resources - model['linkTo'] = this.linkTo - - groovyTemplate.process templatePath, model - } - - /** - * Generate a full URL of the service, mostly for use in templates. - * @see io.spring.initializr.metadata.InitializrConfiguration.Env#forceSsl - */ - protected String generateAppUrl() { - def builder = ServletUriComponentsBuilder.fromCurrentServletMapping() - if (isForceSsl()) { - builder.scheme('https') - } - builder.build() - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/project/MainController.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/project/MainController.groovy deleted file mode 100644 index 5cac19a5..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/project/MainController.groovy +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2012-2017 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 org.springframework.web.servlet.resource.ResourceUrlProvider - -import java.nio.charset.StandardCharsets -import java.util.concurrent.TimeUnit - -import groovy.util.logging.Slf4j -import io.spring.initializr.generator.BasicProjectRequest -import io.spring.initializr.generator.CommandLineHelpGenerator -import io.spring.initializr.generator.ProjectGenerator -import io.spring.initializr.generator.ProjectRequest -import io.spring.initializr.metadata.InitializrMetadataProvider -import io.spring.initializr.util.Agent -import io.spring.initializr.util.GroovyTemplate -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.metadata.DependencyMetadataProvider -import io.spring.initializr.metadata.InitializrMetadata -import io.spring.initializr.util.Version - -import org.springframework.http.CacheControl -import org.springframework.http.HttpHeaders -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import org.springframework.util.DigestUtils -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 - -import static io.spring.initializr.util.Agent.AgentId.CURL -import static io.spring.initializr.util.Agent.AgentId.HTTPIE -import static io.spring.initializr.util.Agent.AgentId.SPRING_BOOT_CLI - -/** - * The main initializr controller provides access to the configured - * metadata and serves as a central endpoint to generate projects - * or build files. - * - * @author Dave Syer - * @author Stephane Nicoll - */ -@Controller -@Slf4j -class MainController extends AbstractInitializrController { - - static final MediaType HAL_JSON_CONTENT_TYPE = MediaType.parseMediaType('application/hal+json') - - private final ProjectGenerator projectGenerator - private final DependencyMetadataProvider dependencyMetadataProvider - private final CommandLineHelpGenerator commandLineHelpGenerator - - MainController(InitializrMetadataProvider metadataProvider, GroovyTemplate groovyTemplate, - ResourceUrlProvider resourceUrlProvider, ProjectGenerator projectGenerator, - DependencyMetadataProvider dependencyMetadataProvider) { - super(metadataProvider, resourceUrlProvider, groovyTemplate) - this.projectGenerator = projectGenerator - this.dependencyMetadataProvider = dependencyMetadataProvider - this.commandLineHelpGenerator = new CommandLineHelpGenerator(groovyTemplate) - } - - @ModelAttribute - BasicProjectRequest projectRequest(@RequestHeader Map headers) { - def request = new ProjectRequest() - request.parameters << headers - request.initialize(metadataProvider.get()) - request - } - - @RequestMapping(value = "/metadata/config", produces = ["application/json"]) - @ResponseBody - InitializrMetadata config() { - metadataProvider.get() - } - - @RequestMapping(value = "/metadata/client") - String client() { - 'redirect:/' - } - - - @RequestMapping(value = "/", produces = ["text/plain"]) - ResponseEntity serviceCapabilitiesText( - @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) { - String appUrl = generateAppUrl() - def metadata = metadataProvider.get() - - def builder = ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN) - if (userAgent) { - Agent agent = Agent.fromUserAgent(userAgent) - if (CURL.equals(agent?.id)) { - def content = commandLineHelpGenerator.generateCurlCapabilities(metadata, appUrl) - return builder.eTag(createUniqueId(content)).body(content) - } - if (HTTPIE.equals(agent?.id)) { - def content = commandLineHelpGenerator.generateHttpieCapabilities(metadata, appUrl) - return builder.eTag(createUniqueId(content)).body(content) - } - if (SPRING_BOOT_CLI.equals(agent?.id)) { - def content = commandLineHelpGenerator.generateSpringBootCliCapabilities(metadata, appUrl) - return builder.eTag(createUniqueId(content)).body(content) - } - } - def content = commandLineHelpGenerator.generateGenericCapabilities(metadata, appUrl) - builder.eTag(createUniqueId(content)).body(content) - } - - @RequestMapping(value = "/", produces = ["application/hal+json"]) - ResponseEntity serviceCapabilitiesHal() { - serviceCapabilitiesFor(InitializrMetadataVersion.V2_1, HAL_JSON_CONTENT_TYPE) - } - - @RequestMapping(value = "/", produces = ["application/vnd.initializr.v2.1+json", "application/json"]) - ResponseEntity serviceCapabilitiesV21() { - serviceCapabilitiesFor(InitializrMetadataVersion.V2_1) - } - - @RequestMapping(value = "/", produces = ["application/vnd.initializr.v2+json"]) - ResponseEntity serviceCapabilitiesV2() { - serviceCapabilitiesFor(InitializrMetadataVersion.V2) - } - - private ResponseEntity serviceCapabilitiesFor(InitializrMetadataVersion version) { - serviceCapabilitiesFor(version, version.mediaType) - } - - private ResponseEntity serviceCapabilitiesFor(InitializrMetadataVersion version, MediaType contentType) { - String appUrl = generateAppUrl() - def content = getJsonMapper(version).write(metadataProvider.get(), appUrl) - return ResponseEntity.ok().contentType(contentType).eTag(createUniqueId(content)) - .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content) - } - - private static InitializrMetadataJsonMapper getJsonMapper(InitializrMetadataVersion version) { - switch (version) { - case InitializrMetadataVersion.V2: return new InitializrMetadataV2JsonMapper(); - default: return new InitializrMetadataV21JsonMapper(); - } - } - - @RequestMapping(value = "/dependencies", produces = ["application/vnd.initializr.v2.1+json", "application/json"]) - ResponseEntity dependenciesV21(@RequestParam(required = false) String bootVersion) { - dependenciesFor(InitializrMetadataVersion.V2_1, bootVersion) - } - - private ResponseEntity dependenciesFor(InitializrMetadataVersion version, String bootVersion) { - def metadata = metadataProvider.get() - Version v = bootVersion != null ? Version.parse(bootVersion) : - Version.parse(metadata.bootVersions.getDefault().id); - def dependencyMetadata = dependencyMetadataProvider.get(metadata, v) - def content = new DependencyMetadataV21JsonMapper().write(dependencyMetadata) - return ResponseEntity.ok().contentType(version.mediaType).eTag(createUniqueId(content)) - .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content) - } - - @RequestMapping(value = '/', produces = 'text/html') - @ResponseBody - String home() { - renderHome('home.html') - } - - @RequestMapping('/spring') - String spring() { - def url = metadataProvider.get().createCliDistributionURl('zip') - "redirect:$url" - } - - @RequestMapping(value = ['/spring.tar.gz', 'spring.tgz']) - String springTgz() { - def url = metadataProvider.get().createCliDistributionURl('tar.gz') - "redirect:$url" - } - - @RequestMapping('/pom') - @ResponseBody - ResponseEntity pom(BasicProjectRequest request) { - request.type = 'maven-build' - def mavenPom = projectGenerator.generateMavenPom((ProjectRequest) request) - createResponseEntity(mavenPom, 'application/octet-stream', 'pom.xml') - } - - @RequestMapping('/build') - @ResponseBody - ResponseEntity gradle(BasicProjectRequest request) { - request.type = 'gradle-build' - def gradleBuild = projectGenerator.generateGradleBuild((ProjectRequest) request) - createResponseEntity(gradleBuild, 'application/octet-stream', 'build.gradle') - } - - @RequestMapping('/starter.zip') - @ResponseBody - ResponseEntity springZip(BasicProjectRequest basicRequest) { - ProjectRequest request = (ProjectRequest) basicRequest - def dir = projectGenerator.generateProjectStructure(request) - - def download = projectGenerator.createDistributionFile(dir, '.zip') - - def wrapperScript = getWrapperScript(request) - - new AntBuilder().zip(destfile: download) { - zipfileset(dir: dir, includes: wrapperScript, filemode: 755) - zipfileset(dir: dir, includes: '**,', excludes: wrapperScript, defaultexcludes: 'no') - } - upload(download, dir, generateFileName(request, 'zip'), 'application/zip') - } - - @RequestMapping(value = '/starter.tgz', produces = 'application/x-compress') - @ResponseBody - ResponseEntity springTgz(BasicProjectRequest basicRequest) { - ProjectRequest request = (ProjectRequest) basicRequest - def dir = projectGenerator.generateProjectStructure(request) - - def download = projectGenerator.createDistributionFile(dir, '.tgz') - - def wrapperScript = getWrapperScript(request) - - new AntBuilder().tar(destfile: download, compression: 'gzip') { - zipfileset(dir: dir, includes: wrapperScript, filemode: 755) - zipfileset(dir: dir, includes: '**', excludes: wrapperScript, defaultexcludes: 'no') - } - upload(download, dir, generateFileName(request, 'tgz'), 'application/x-compress') - } - - private static String generateFileName(ProjectRequest request, String extension) { - String tmp = request.artifactId.replaceAll(' ', '_') - URLEncoder.encode(tmp, 'UTF-8') + '.' + extension - } - - private static String getWrapperScript(ProjectRequest request) { - def script = 'gradle'.equals(request.build) ? 'gradlew' : 'mvnw' - def wrapperScript = request.baseDir ? "$request.baseDir/$script" : script - wrapperScript - } - - private ResponseEntity upload(File download, File dir, String fileName, String contentType) { - log.info("Uploading: ${download} (${download.bytes.length} bytes)") - ResponseEntity result = createResponseEntity(download.bytes, contentType, fileName) - projectGenerator.cleanTempFiles(dir) - result - } - - private ResponseEntity createResponseEntity(byte[] content, String contentType, String fileName) { - String contentDispositionValue = "attachment; filename=\"$fileName\"" - ResponseEntity.ok() - .header('Content-Type', contentType) - .header('Content-Disposition', contentDispositionValue) - .body(content); - } - - private String createUniqueId(String content) { - StringBuilder builder = new StringBuilder() - DigestUtils.appendMd5DigestAsHex(content.getBytes(StandardCharsets.UTF_8), builder) - builder.toString() - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/support/DefaultDependencyMetadataProvider.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/support/DefaultDependencyMetadataProvider.groovy deleted file mode 100644 index 3535808b..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/support/DefaultDependencyMetadataProvider.groovy +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2012-2017 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.support - -import io.spring.initializr.metadata.BillOfMaterials -import io.spring.initializr.metadata.Dependency -import io.spring.initializr.metadata.DependencyMetadata -import io.spring.initializr.metadata.DependencyMetadataProvider -import io.spring.initializr.metadata.InitializrMetadata -import io.spring.initializr.metadata.Repository -import io.spring.initializr.util.Version - -import org.springframework.cache.annotation.Cacheable - -/** - * A default {@link DependencyMetadataProvider} implementation. - * - * @author Stephane Nicoll - */ -class DefaultDependencyMetadataProvider implements DependencyMetadataProvider { - - @Override - @Cacheable(cacheNames = "dependency-metadata", key = "#p1") - DependencyMetadata get(InitializrMetadata metadata, Version bootVersion) { - Map dependencies = [:] - for (Dependency d : metadata.dependencies.getAll()) { - if (d.match(bootVersion)) { - dependencies[d.id] = d.resolve(bootVersion) - } - } - - Map repositories = [:] - for (Dependency d : dependencies.values()) { - if (d.repository) { - repositories[d.repository] = metadata.configuration.env.repositories[d.repository] - } - } - - Map boms = [:] - for (Dependency d : dependencies.values()) { - if (d.bom) { - boms[d.bom] = metadata.configuration.env.boms.get(d.bom).resolve(bootVersion) - } - } - // Each resolved bom may require additional repositories - for (BillOfMaterials b : boms.values()) { - for (String id : b.repositories) { - repositories[id] = metadata.configuration.env.repositories[id] - } - } - - return new DependencyMetadata(bootVersion, dependencies, repositories, boms) - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/support/DefaultInitializrMetadataProvider.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/support/DefaultInitializrMetadataProvider.groovy deleted file mode 100644 index 3ac398f1..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/support/DefaultInitializrMetadataProvider.groovy +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012-2017 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.support - -import groovy.util.logging.Slf4j -import io.spring.initializr.metadata.DefaultMetadataElement -import io.spring.initializr.metadata.InitializrMetadata -import io.spring.initializr.metadata.InitializrMetadataProvider - -import org.springframework.cache.annotation.Cacheable -import org.springframework.web.client.RestTemplate - -/** - * A default {@link InitializrMetadataProvider} that is able to refresh - * the metadata with the status of the main spring.io site. - * - * @author Stephane Nicoll - */ -@Slf4j -class DefaultInitializrMetadataProvider implements InitializrMetadataProvider { - - private final InitializrMetadata metadata - private final RestTemplate restTemplate - - DefaultInitializrMetadataProvider(InitializrMetadata metadata, RestTemplate restTemplate) { - this.metadata = metadata - this.restTemplate = restTemplate - } - - @Override - @Cacheable(value = 'initializr', key = "'metadata'") - InitializrMetadata get() { - updateInitializrMetadata(metadata) - metadata - } - - protected void updateInitializrMetadata(InitializrMetadata metadata) { - def bootVersions = fetchBootVersions() - if (bootVersions) { - if (!bootVersions.find { it.default }) { // No default specified - bootVersions[0].default = true - } - metadata.updateSpringBootVersions(bootVersions) - } - } - - protected List fetchBootVersions() { - def url = metadata.configuration.env.springBootMetadataUrl - if (url) { - try { - log.info("Fetching boot metadata from $url") - return new SpringBootMetadataReader(restTemplate, url).bootVersions - } catch (Exception e) { - log.warn('Failed to fetch spring boot metadata', e) - } - } - null - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/support/SpringBootMetadataReader.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/support/SpringBootMetadataReader.groovy deleted file mode 100644 index a69bb10b..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/support/SpringBootMetadataReader.groovy +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-2017 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.support - -import groovy.json.JsonSlurper -import io.spring.initializr.metadata.DefaultMetadataElement - -import org.springframework.web.client.RestTemplate - -/** - * Reads metadata from the main spring.io website. This is a stateful - * service: creates a new instance whenever you need to refresh the - * content. - * - * @author Stephane Nicoll - */ -class SpringBootMetadataReader { - - private final def content - - /** - * Parse the content of the metadata at the specified url - */ - SpringBootMetadataReader(RestTemplate restTemplate, String url) { - def content = restTemplate.getForObject(url, String.class) - this.content = new JsonSlurper().parseText(content) - } - - /** - * Return the boot versions parsed by this instance. - */ - List getBootVersions() { - content.projectReleases.collect { - def version = new DefaultMetadataElement() - version.id = it.version - def name = it.versionDisplayName - version.name = (it.snapshot ? name + ' (SNAPSHOT)' : name) - version.setDefault(it.current) - version - } - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/ui/UiController.groovy b/initializr-web/src/main/groovy/io/spring/initializr/web/ui/UiController.groovy deleted file mode 100644 index 4bc97cc4..00000000 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/ui/UiController.groovy +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2012-2017 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.ui - -import java.nio.charset.StandardCharsets - -import groovy.json.JsonBuilder -import io.spring.initializr.metadata.Dependency -import io.spring.initializr.metadata.InitializrMetadataProvider -import io.spring.initializr.util.Version - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.util.DigestUtils -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController - -/** - * UI specific controller providing dedicated endpoints for the Web UI. - * - * @author Stephane Nicoll - */ -@RestController -class UiController { - - @Autowired - protected InitializrMetadataProvider metadataProvider - - @RequestMapping(value = "/ui/dependencies", produces = ["application/json"]) - ResponseEntity dependencies(@RequestParam(required = false) String version) { - def dependencyGroups = metadataProvider.get().dependencies.content - def content = [] - Version v = version ? Version.parse(version) : null - dependencyGroups.each { g -> - g.content.each { d -> - if (v && d.versionRange) { - if (d.match(v)) { - content << new DependencyItem(g.name, d) - } - } else { - content << new DependencyItem(g.name, d) - } - } - } - def json = writeDependencies(content) - ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON). - eTag(createUniqueId(json)).body(json) - } - - private static String writeDependencies(List items) { - JsonBuilder json = new JsonBuilder(); - json { - dependencies items.collect { d -> - mapDependency(d) - } - } - json.toString() - } - - private static mapDependency(DependencyItem item) { - def result = [:] - Dependency d = item.dependency - result.id = d.id - result.name = d.name - result.group = item.group - if (d.description) { - result.description = d.description - } - if (d.weight) { - result.weight = d.weight - } - if (d.keywords || d.aliases) { - def all = d.keywords + d.aliases - result.keywords = all.join(',') - } - result - } - - private static class DependencyItem { - private final String group - private final Dependency dependency - - DependencyItem(String group, Dependency dependency) { - this.group = group - this.dependency = dependency - } - } - - private String createUniqueId(String content) { - StringBuilder builder = new StringBuilder() - DigestUtils.appendMd5DigestAsHex(content.getBytes(StandardCharsets.UTF_8), builder) - builder.toString() - } - -} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessor.groovy b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessor.java similarity index 57% rename from initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessor.groovy rename to initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessor.java index 9bac0094..5a3b430a 100644 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessor.groovy +++ b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessor.java @@ -14,19 +14,22 @@ * limitations under the License. */ -package io.spring.initializr.web.autoconfigure +package io.spring.initializr.web.autoconfigure; -import org.springframework.boot.SpringApplication -import org.springframework.boot.context.config.ConfigFileApplicationListener -import org.springframework.boot.env.EnvironmentPostProcessor -import org.springframework.core.Ordered -import org.springframework.core.env.ConfigurableEnvironment -import org.springframework.core.env.MapPropertySource -import org.springframework.core.env.MutablePropertySources -import org.springframework.core.env.PropertySource -import org.springframework.util.StringUtils -import org.springframework.web.util.UriComponents -import org.springframework.web.util.UriComponentsBuilder +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigFileApplicationListener; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; /** * Post-process the environment to extract the service credentials provided by @@ -34,7 +37,8 @@ import org.springframework.web.util.UriComponentsBuilder * * @author Stephane Nicoll */ -class CloudfoundryEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { +public class CloudfoundryEnvironmentPostProcessor + implements EnvironmentPostProcessor, Ordered { private static final String PROPERTY_SOURCE_NAME = "defaultProperties"; @@ -44,18 +48,18 @@ class CloudfoundryEnvironmentPostProcessor implements EnvironmentPostProcessor, public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication springApplication) { - Map map = [:] - String uri = environment.getProperty('vcap.services.stats-index.credentials.uri') + Map map = new LinkedHashMap<>(); + String uri = environment.getProperty("vcap.services.stats-index.credentials.uri"); if (StringUtils.hasText(uri)) { - UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build() - def userInfo = uriComponents.getUserInfo() - if (userInfo) { - String[] credentials = userInfo.split(':') - map['initializr.stats.elastic.username'] = credentials[0] - map['initializr.stats.elastic.password'] = credentials[1] + UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build(); + String userInfo = uriComponents.getUserInfo(); + if (StringUtils.hasText(userInfo)) { + String[] credentials = userInfo.split(":"); + map.put("initializr.stats.elastic.username", credentials[0]); + map.put("initializr.stats.elastic.password", credentials[1]); } - map['initializr.stats.elastic.uri'] = UriComponentsBuilder.fromUriString(uri) - .userInfo(null).build().toString() + map.put("initializr.stats.elastic.uri", UriComponentsBuilder.fromUriString(uri) + .userInfo(null).build().toString()); addOrReplace(environment.getPropertySources(), map); } @@ -63,19 +67,19 @@ class CloudfoundryEnvironmentPostProcessor implements EnvironmentPostProcessor, @Override public int getOrder() { - return this.order + return this.order; } private static void addOrReplace(MutablePropertySources propertySources, Map map) { - MapPropertySource target = null + MapPropertySource target = null; if (propertySources.contains(PROPERTY_SOURCE_NAME)) { - PropertySource source = propertySources.get(PROPERTY_SOURCE_NAME) + PropertySource source = propertySources.get(PROPERTY_SOURCE_NAME); if (source instanceof MapPropertySource) { - target = (MapPropertySource) source + target = (MapPropertySource) source; for (String key : map.keySet()) { if (!target.containsProperty(key)) { - target.getSource().put(key, map.get(key)) + target.getSource().put(key, map.get(key)); } } } 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 new file mode 100644 index 00000000..97bb689e --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-2017 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.autoconfigure; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.cache.support.SimpleCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.resource.ResourceUrlProvider; + +import com.github.benmanes.caffeine.cache.Caffeine; + +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.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.GroovyTemplate; +import io.spring.initializr.web.project.MainController; +import io.spring.initializr.web.support.DefaultDependencyMetadataProvider; +import io.spring.initializr.web.support.DefaultInitializrMetadataProvider; +import io.spring.initializr.web.ui.UiController; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} to configure Spring initializr. In a web environment, + * configures the necessary controller to serve the applications from the + * root context. + * + *

Project generation can be customized by defining a custom + * {@link ProjectGenerator}. + * + * @author Stephane Nicoll + */ +@Configuration +@EnableCaching +@EnableConfigurationProperties(InitializrProperties.class) +public class InitializrAutoConfiguration { + + @Autowired(required = false) + private List postProcessors = new ArrayList<>(); + + @Bean + public WebConfig webConfig() { + return new WebConfig(); + } + + @Bean + @ConditionalOnMissingBean + public MainController initializrMainController(InitializrMetadataProvider metadataProvider, + GroovyTemplate groovyTemplate, + ResourceUrlProvider resourceUrlProvider, + ProjectGenerator projectGenerator, + DependencyMetadataProvider dependencyMetadataProvider) { + return new MainController(metadataProvider, groovyTemplate, resourceUrlProvider + , projectGenerator, dependencyMetadataProvider); + } + + @Bean + @ConditionalOnMissingBean + public UiController initializrUiController() { + return new UiController(); + } + + @Bean + @ConditionalOnMissingBean + public ProjectGenerator projectGenerator() { + return new ProjectGenerator(); + } + + @Bean + @ConditionalOnMissingBean + public GroovyTemplate groovyTemplate(Environment environment) { + RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment, "spring.groovy.template."); + boolean cache = resolver.getProperty("cache", Boolean.class, true); + GroovyTemplate groovyTemplate = new GroovyTemplate(); + groovyTemplate.setCache(cache); + return groovyTemplate; + } + + @Bean + @ConditionalOnMissingBean + public ProjectRequestResolver projectRequestResolver() { + return new ProjectRequestResolver(postProcessors); + } + + @Bean + public ProjectResourceLocator projectResourceLocator() { + return new ProjectResourceLocator(); + } + + @Bean + @ConditionalOnMissingBean(InitializrMetadataProvider.class) + public InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties) { + InitializrMetadata metadata = InitializrMetadataBuilder.fromInitializrProperties(properties).build(); + return new DefaultInitializrMetadataProvider(metadata, new RestTemplate()); + } + + @Bean + @ConditionalOnMissingBean + public DependencyMetadataProvider dependencyMetadataProvider() { + return new DefaultDependencyMetadataProvider(); + } + + @Bean + @ConditionalOnMissingBean + public CacheManager cacheManager() { + SimpleCacheManager cacheManager = new SimpleCacheManager(); + cacheManager.setCaches(Arrays.asList( + createConcurrentMapCache(600L, "initializr"), + new ConcurrentMapCache("dependency-metadata"), + new ConcurrentMapCache("project-resources"))); + return cacheManager; + } + + private static Cache createConcurrentMapCache(Long timeToLive, String name) { + return new CaffeineCache(name, Caffeine + .newBuilder() + .expireAfterWrite(timeToLive, TimeUnit.SECONDS) + .build()); + } + +} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/WebConfig.groovy b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/WebConfig.java similarity index 56% rename from initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/WebConfig.groovy rename to initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/WebConfig.java index d9d9b153..d96714e8 100644 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/autoconfigure/WebConfig.groovy +++ b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/WebConfig.java @@ -14,34 +14,36 @@ * limitations under the License. */ -package io.spring.initializr.web.autoconfigure +package io.spring.initializr.web.autoconfigure; -import javax.servlet.http.HttpServletRequest +import java.util.Collections; +import java.util.List; -import io.spring.initializr.util.Agent +import javax.servlet.http.HttpServletRequest; -import org.springframework.http.HttpHeaders -import org.springframework.http.MediaType -import org.springframework.web.HttpMediaTypeNotAcceptableException -import org.springframework.web.accept.ContentNegotiationStrategy -import org.springframework.web.context.request.NativeWebRequest -import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter -import org.springframework.web.util.UrlPathHelper +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.util.UrlPathHelper; -import static io.spring.initializr.util.Agent.AgentId.CURL -import static io.spring.initializr.util.Agent.AgentId.HTTPIE +import io.spring.initializr.util.Agent; +import io.spring.initializr.util.Agent.AgentId; /** * Spring Initializr web configuration. * * @author Stephane Nicoll */ -class WebConfig extends WebMvcConfigurerAdapter { +public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { - configurer.defaultContentTypeStrategy(new CommandLineContentNegotiationStrategy()) + configurer.defaultContentTypeStrategy(new CommandLineContentNegotiationStrategy()); } /** @@ -53,20 +55,22 @@ class WebConfig extends WebMvcConfigurerAdapter { private final UrlPathHelper urlPathHelper = new UrlPathHelper(); @Override - List resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + public List resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { String path = urlPathHelper.getPathWithinApplication( request.getNativeRequest(HttpServletRequest.class)); - if (!path || !path.equals("/")) { // Only care about "/" - return Collections.emptyList() + if (!StringUtils.hasText(path) || !path.equals("/")) { // Only care about "/" + return Collections.emptyList(); } - String userAgent = request.getHeader(HttpHeaders.USER_AGENT) - if (userAgent) { - Agent agent = Agent.fromUserAgent(userAgent) - if (CURL.equals(agent?.id) || HTTPIE.equals(agent?.id)) { - return Collections.singletonList(MediaType.TEXT_PLAIN) + String userAgent = request.getHeader(HttpHeaders.USER_AGENT); + if (userAgent!=null) { + Agent agent = Agent.fromUserAgent(userAgent); + if (agent!=null) { + if (AgentId.CURL.equals(agent.getId()) || AgentId.HTTPIE.equals(agent.getId())) { + return Collections.singletonList(MediaType.TEXT_PLAIN); + } } } - return Collections.singletonList(MediaType.APPLICATION_JSON) + return Collections.singletonList(MediaType.APPLICATION_JSON); } } diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/DependencyMetadataJsonMapper.groovy b/initializr-web/src/main/java/io/spring/initializr/web/mapper/DependencyMetadataJsonMapper.java similarity index 89% rename from initializr-web/src/main/groovy/io/spring/initializr/web/mapper/DependencyMetadataJsonMapper.groovy rename to initializr-web/src/main/java/io/spring/initializr/web/mapper/DependencyMetadataJsonMapper.java index 9c6f4d70..d65b512c 100644 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/DependencyMetadataJsonMapper.groovy +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/DependencyMetadataJsonMapper.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.spring.initializr.web.mapper +package io.spring.initializr.web.mapper; -import io.spring.initializr.metadata.DependencyMetadata +import io.spring.initializr.metadata.DependencyMetadata; /** * Generate a JSON representation of a set of dependencies. diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/DependencyMetadataV21JsonMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/DependencyMetadataV21JsonMapper.java new file mode 100644 index 00000000..f76c2925 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/DependencyMetadataV21JsonMapper.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2017 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.mapper; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.json.JSONObject; + +import io.spring.initializr.metadata.BillOfMaterials; +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.DependencyMetadata; +import io.spring.initializr.metadata.Repository; + +/** + * A {@link DependencyMetadataJsonMapper} handling the metadata format for v2.1. + * + * @author Stephane Nicoll + */ +public class DependencyMetadataV21JsonMapper implements DependencyMetadataJsonMapper { + + @Override + public String write(DependencyMetadata metadata) { + JSONObject json = new JSONObject(); + json.put("bootVersion", metadata.getBootVersion().toString()); + json.put("dependencies", + metadata.getDependencies().entrySet().stream() + .collect(Collectors.toMap(entry -> entry.getKey(), + entry -> mapDependency(entry.getValue())))); + json.put("repositories", + metadata.getRepositories().entrySet().stream() + .collect(Collectors.toMap(entry -> entry.getKey(), + entry -> mapRepository(entry.getValue())))); + json.put("boms", metadata.getBoms().entrySet().stream().collect(Collectors + .toMap(entry -> entry.getKey(), entry -> mapBom(entry.getValue())))); + return json.toString(); + } + + private static Map mapDependency(Dependency dep) { + Map result = new LinkedHashMap<>(); + result.put("groupId", dep.getGroupId()); + result.put("artifactId", dep.getArtifactId()); + if (dep.getVersion() != null) { + result.put("version", dep.getVersion()); + } + result.put("scope", dep.getScope()); + if (dep.getBom() != null) { + result.put("bom", dep.getBom()); + } + if (dep.getRepository() != null) { + result.put("repository", dep.getRepository()); + } + return result; + } + + private static Map mapRepository(Repository repo) { + Map result = new LinkedHashMap<>(); + result.put("name", repo.getName()); + result.put("url", repo.getUrl()); + result.put("snapshotEnabled", repo.isSnapshotsEnabled()); + return result; + } + + private static Map mapBom(BillOfMaterials bom) { + Map result = new LinkedHashMap<>(); + result.put("groupId", bom.getGroupId()); + result.put("artifactId", bom.getArtifactId()); + if (bom.getVersion() != null) { + result.put("version", bom.getVersion()); + } + if (bom.getRepositories() != null) { + result.put("repositories", bom.getRepositories()); + } + return result; + } + +} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataJsonMapper.groovy b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapper.java similarity index 85% rename from initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataJsonMapper.groovy rename to initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapper.java index 13ca53e5..99774673 100644 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataJsonMapper.groovy +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapper.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package io.spring.initializr.web.mapper +package io.spring.initializr.web.mapper; -import io.spring.initializr.metadata.InitializrMetadata +import io.spring.initializr.metadata.InitializrMetadata; /** * Generate a JSON representation of the metadata. * * @author Stephane Nicoll */ -interface InitializrMetadataJsonMapper { +public interface InitializrMetadataJsonMapper { /** * Write a json representation of the specified metadata. diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.java new file mode 100644 index 00000000..88bc9a2b --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2017 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.mapper; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.json.JSONObject; +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.TemplateVariables; +import org.springframework.hateoas.UriTemplate; + +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.Type; + +/** + * A {@link InitializrMetadataJsonMapper} handling the metadata format for v2.1 + *

+ * Version 2.1 brings the "versionRange" attribute for a dependency to restrict + * the Spring Boot versions that can be used against it. That version also adds + * an additional `dependencies` endpoint. + * + * @author Stephane Nicoll + */ +public class InitializrMetadataV21JsonMapper extends InitializrMetadataV2JsonMapper { + + private final TemplateVariables dependenciesVariables; + + public InitializrMetadataV21JsonMapper() { + this.dependenciesVariables = new TemplateVariables( + new TemplateVariable("bootVersion", TemplateVariable.VariableType.REQUEST_PARAM) + ); + } + + @Override + protected Map links(JSONObject parent, List types, String appUrl) { + Map links = super.links(parent, types, appUrl); + links.put("dependencies", dependenciesLink(appUrl)); + parent.put("_links", links); + return links; + } + + @Override + protected Map mapDependency(Dependency dependency) { + Map content = mapValue(dependency); + if (dependency.getVersionRange()!=null) { + content.put("versionRange", dependency.getVersionRange()); + } + if (dependency.getLinks()!=null && !dependency.getLinks().isEmpty()) { + content.put("_links", LinkMapper.mapLinks(dependency.getLinks())); + } + return content; + } + + private Map dependenciesLink(String appUrl) { + String uri = appUrl != null ? appUrl + "/dependencies" : "/dependencies"; + UriTemplate uriTemplate = new UriTemplate(uri, this.dependenciesVariables); + Map result = new LinkedHashMap<>(); + result.put("href", uriTemplate.toString()); + result.put("templated", true); + return result; + } +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.java new file mode 100644 index 00000000..0f4a1071 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.java @@ -0,0 +1,203 @@ +/* + * Copyright 2012-2017 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.mapper; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.json.JSONObject; +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.TemplateVariables; +import org.springframework.hateoas.UriTemplate; +import org.springframework.util.StringUtils; + +import io.spring.initializr.metadata.DefaultMetadataElement; +import io.spring.initializr.metadata.DependenciesCapability; +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.DependencyGroup; +import io.spring.initializr.metadata.Describable; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.MetadataElement; +import io.spring.initializr.metadata.SingleSelectCapability; +import io.spring.initializr.metadata.TextCapability; +import io.spring.initializr.metadata.Type; +import io.spring.initializr.metadata.TypeCapability; + +/** + * A {@link InitializrMetadataJsonMapper} handling the metadata format for v2. + * + * @author Stephane Nicoll + */ +public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMapper { + + private final TemplateVariables templateVariables; + + public InitializrMetadataV2JsonMapper() { + this.templateVariables = new TemplateVariables( + new TemplateVariable("dependencies", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("packaging", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("javaVersion", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("language", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("bootVersion", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("groupId", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("artifactId", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("version", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("name", TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("description", + TemplateVariable.VariableType.REQUEST_PARAM), + new TemplateVariable("packageName", + TemplateVariable.VariableType.REQUEST_PARAM)); + } + + @Override + public String write(InitializrMetadata metadata, String appUrl) { + JSONObject delegate = new JSONObject(); + links(delegate, metadata.getTypes().getContent(), appUrl); + dependencies(delegate, metadata.getDependencies()); + type(delegate, metadata.getTypes()); + singleSelect(delegate, metadata.getPackagings()); + singleSelect(delegate, metadata.getJavaVersions()); + singleSelect(delegate, metadata.getLanguages()); + singleSelect(delegate, metadata.getBootVersions()); + text(delegate, metadata.getGroupId()); + text(delegate, metadata.getArtifactId()); + text(delegate, metadata.getVersion()); + text(delegate, metadata.getName()); + text(delegate, metadata.getDescription()); + text(delegate, metadata.getPackageName()); + return delegate.toString(); + } + + protected Map links(JSONObject parent, List types, String appUrl) { + Map content = new LinkedHashMap<>(); + types.forEach(it -> { + content.put(it.getId(), link(appUrl, it)); + }); + parent.put("_links", content); + return content; + } + + protected Map link(String appUrl, Type type) { + Map result = new LinkedHashMap<>(); + result.put("href", generateTemplatedUri(appUrl, type)); + result.put("templated", true); + return result; + } + + private String generateTemplatedUri(String appUrl, Type type) { + String uri = appUrl != null ? appUrl + type.getAction() : type.getAction(); + uri = uri + "?type=" + type.getId(); + UriTemplate uriTemplate = new UriTemplate(uri, this.templateVariables); + return uriTemplate.toString(); + } + + protected void dependencies(JSONObject parent, DependenciesCapability capability) { + Map map = new LinkedHashMap<>(); + map.put("type", capability.getType().getName()); + map.put("values", + capability.getContent().stream().map(it -> mapDependencyGroup(it)).collect(Collectors.toList())); + parent.put(capability.getId(), map); + } + + protected void type(JSONObject parent, TypeCapability capability) { + Map map = new LinkedHashMap<>(); + map.put("type", "action"); + Type defaultType = capability.getDefault(); + if (defaultType != null) { + map.put("default", defaultType.getId()); + } + map.put("values", capability.getContent().stream().map(it -> mapType(it)) + .collect(Collectors.toList())); + parent.put("type", map); + } + + protected void singleSelect(JSONObject parent, SingleSelectCapability capability) { + Map map = new LinkedHashMap<>(); + map.put("type", capability.getType().getName()); + DefaultMetadataElement defaultType = capability.getDefault(); + if (defaultType != null) { + map.put("default", defaultType.getId()); + } + map.put("values", capability.getContent().stream().map(it -> mapValue(it)) + .collect(Collectors.toList())); + parent.put(capability.getId(), map); + } + + protected void text(JSONObject parent, TextCapability capability) { + Map map = new LinkedHashMap<>(); + map.put("type", capability.getType().getName()); + String defaultValue = capability.getContent(); + if (StringUtils.hasText(defaultValue)) { + map.put("default", defaultValue); + } + parent.put(capability.getId(), map); + } + + protected Map mapDependencyGroup(DependencyGroup group) { + Map result = new LinkedHashMap<>(); + result.put("name", group.getName()); + if ((group instanceof Describable) && ((Describable) group).getDescription() != null) { + result.put("description", ((Describable) group).getDescription()); + } + List items = new ArrayList<>(); + group.getContent().forEach(it -> { + Map dependency = mapDependency(it); + if (dependency != null) { + items.add(dependency); + } + }); + result.put("values", items); + return result; + } + + protected Map mapDependency(Dependency dependency) { + if (dependency.getVersionRange() == null) { + // only map the dependency if no versionRange is set + return mapValue(dependency); + } + return null; + } + + protected Map mapType(Type type) { + Map result = mapValue(type); + result.put("action", type.getAction()); + result.put("tags", type.getTags()); + return result; + } + + protected Map mapValue(MetadataElement value) { + Map result = new LinkedHashMap<>(); + result.put("id", value.getId()); + result.put("name", value.getName()); + if ((value instanceof Describable) && ((Describable) value).getDescription() != null) { + result.put("description", ((Describable) value).getDescription()); + } + return result; + } + +} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataVersion.groovy b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java similarity index 63% rename from initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataVersion.groovy rename to initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java index e0e5f79f..2712f32f 100644 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/mapper/InitializrMetadataVersion.groovy +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java @@ -14,37 +14,37 @@ * limitations under the License. */ -package io.spring.initializr.web.mapper +package io.spring.initializr.web.mapper; -import org.springframework.http.MediaType +import org.springframework.http.MediaType; /** * Define the supported metadata version. * * @author Stephane Nicoll */ -enum InitializrMetadataVersion { +public enum InitializrMetadataVersion { /** * HAL-compliant metadata. */ - V2('application/vnd.initializr.v2+json'), + V2("application/vnd.initializr.v2+json"), /** - * Add 'versionRange' attribute to any dependency to specify which + * Add "versionRange" attribute to any dependency to specify which * Spring Boot versions are compatible with it. Also provide a - * separate 'dependencies' endpoint to query dependencies metadata. + * separate "dependencies" endpoint to query dependencies metadata. */ - V2_1('application/vnd.initializr.v2.1+json') + V2_1("application/vnd.initializr.v2.1+json"); private final MediaType mediaType; - InitializrMetadataVersion(String mediaType) { - this.mediaType = MediaType.parseMediaType(mediaType) + private InitializrMetadataVersion(String mediaType) { + this.mediaType = MediaType.parseMediaType(mediaType); } - MediaType getMediaType() { - return mediaType + public MediaType getMediaType() { + return mediaType; } } \ No newline at end of file diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/LinkMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/LinkMapper.java new file mode 100644 index 00000000..5f37aeed --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/LinkMapper.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2017 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.mapper; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.spring.initializr.metadata.Link; + +/** + * Generate a json representation for {@link Link} + * + * @author Stephane Nicoll + */ +public class LinkMapper { + + /** + * Map the specified links to a json model. If several links share the same relation, + * they are grouped together. + * @param links the links to map + * @return a model for the specified links + */ + public static Map mapLinks(List links) { + Map result = new LinkedHashMap<>(); + Map> byRel = new LinkedHashMap<>(); + links.forEach(it -> { + byRel.computeIfAbsent(it.getRel(), k -> new ArrayList<>()).add(it); + }); + byRel.forEach((rel, l) -> { + if (l.size() == 1) { + Map root = new LinkedHashMap<>(); + mapLink(l.get(0), root); + result.put(rel, root); + } + else { + List> root = new ArrayList<>(); + l.forEach(link -> { + Map model = new LinkedHashMap<>(); + mapLink(link, model); + root.add(model); + }); + result.put(rel, root); + } + }); + return result; + } + + private static void mapLink(Link link, Map model) { + model.put("href", link.getHref()); + if (link.isTemplated()) { + model.put("templated", true); + } + if (link.getDescription() != null) { + model.put("title", link.getDescription()); + } + } + +} 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/project/AbstractInitializrController.java new file mode 100644 index 00000000..6667e704 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/AbstractInitializrController.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2017 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.beans.PropertyDescriptor; +import java.io.IOException; +import java.util.Map; +import java.util.function.Function; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.resource.ResourceUrlProvider; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import io.spring.initializr.generator.InvalidProjectRequestException; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.metadata.TypeCapability; + +/** + * A base controller that uses a {@link InitializrMetadataProvider} + * + * @author Stephane Nicoll + */ +public abstract class AbstractInitializrController { + + protected final InitializrMetadataProvider metadataProvider; + private final Function linkTo; + private Boolean forceSsl; + + protected AbstractInitializrController(InitializrMetadataProvider metadataProvider, + ResourceUrlProvider resourceUrlProvider) { + this.metadataProvider = metadataProvider; + this.linkTo = link -> { + String result = resourceUrlProvider.getForLookupPath(link); + return result == null ? link : result; + }; + } + + public boolean isForceSsl() { + if (this.forceSsl == null) { + this.forceSsl = 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()); + } + + /** + * Render the home page with the specified template. + */ + protected void renderHome(Map model) throws Exception { + InitializrMetadata metadata = metadataProvider.get(); + + model.put("serviceUrl", generateAppUrl()); + BeanWrapperImpl wrapper = new BeanWrapperImpl(metadata); + for (PropertyDescriptor descriptor : wrapper.getPropertyDescriptors()) { + if ("types".equals(descriptor.getName())) { + model.put("types", removeTypes(metadata.getTypes())); + } else { + model.put(descriptor.getName(), wrapper.getPropertyValue(descriptor.getName())); + } + } + + // Google analytics support + model.put("trackingCode", + metadata.getConfiguration().getEnv().getGoogleAnalyticsTrackingCode()); + + } + + public Function getLinkTo() { + return linkTo; + } + + private TypeCapability removeTypes(TypeCapability types) { + TypeCapability result = new TypeCapability(); + result.setDescription(types.getDescription()); + result.setTitle(types.getTitle()); + result.getContent().addAll(types.getContent()); + // Only keep project type + result.getContent().removeIf(t -> !"project".equals(t.getTags().get("format")) ); + return result; + } + + /** + * Generate a full URL of the service, mostly for use in templates. + * @see io.spring.initializr.metadata.InitializrConfiguration.Env#forceSsl + */ + protected String generateAppUrl() { + ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping(); + if (isForceSsl()) { + builder.scheme("https"); + } + return builder.build().toString(); + } + +} diff --git a/initializr-web/src/main/groovy/io/spring/initializr/web/project/LegacyStsController.groovy b/initializr-web/src/main/java/io/spring/initializr/web/project/LegacyStsController.java similarity index 52% rename from initializr-web/src/main/groovy/io/spring/initializr/web/project/LegacyStsController.groovy rename to initializr-web/src/main/java/io/spring/initializr/web/project/LegacyStsController.java index e44d400c..367869d1 100644 --- a/initializr-web/src/main/groovy/io/spring/initializr/web/project/LegacyStsController.groovy +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/LegacyStsController.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package io.spring.initializr.web.project +package io.spring.initializr.web.project; -import io.spring.initializr.metadata.InitializrMetadataProvider -import io.spring.initializr.util.GroovyTemplate +import java.util.Map; -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.ResponseBody -import org.springframework.web.servlet.resource.ResourceUrlProvider +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.resource.ResourceUrlProvider; + +import io.spring.initializr.metadata.InitializrMetadataProvider; /** * A controller used to serve the legacy home page used by STS. @@ -30,18 +30,17 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider * @author Stephane Nicoll */ @Controller -@Deprecated -class LegacyStsController extends AbstractInitializrController { +public class LegacyStsController extends AbstractInitializrController { - LegacyStsController(InitializrMetadataProvider metadataProvider, ResourceUrlProvider resourceUrlProvider, - GroovyTemplate groovyTemplate) { - super(metadataProvider, resourceUrlProvider, groovyTemplate) + public LegacyStsController(InitializrMetadataProvider metadataProvider, ResourceUrlProvider resourceUrlProvider) { + super(metadataProvider, resourceUrlProvider); } - - @RequestMapping(value = '/sts', produces = 'text/html') - @ResponseBody - String stsHome() { - renderHome('sts-home.html') + + @RequestMapping(value = "/sts", produces = "text/html") + public String stsHome(Map model) throws Exception { + renderHome(model); + return "sts-home"; } } + 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/project/MainController.java new file mode 100644 index 00000000..2fbff8a0 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java @@ -0,0 +1,330 @@ +/* + * Copyright 2012-2017 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 static io.spring.initializr.util.Agent.AgentId.CURL; +import static io.spring.initializr.util.Agent.AgentId.HTTPIE; +import static io.spring.initializr.util.Agent.AgentId.SPRING_BOOT_CLI; + +import java.io.File; +import java.io.FileInputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.rauschig.jarchivelib.ArchiverFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.CacheControl; +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.util.DigestUtils; +import org.springframework.util.StreamUtils; +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; +import org.springframework.web.servlet.resource.ResourceUrlProvider; + +import com.samskivert.mustache.Mustache; + +import io.spring.initializr.generator.BasicProjectRequest; +import io.spring.initializr.generator.CommandLineHelpGenerator; +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; +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.util.Agent; +import io.spring.initializr.util.GroovyTemplate; +import io.spring.initializr.util.Version; +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; + +/** + * The main initializr controller provides access to the configured metadata and serves as + * a central endpoint to generate projects or build files. + * + * @author Dave Syer + * @author Stephane Nicoll + */ +@Controller +public class MainController extends AbstractInitializrController { + + private static final Logger log = LoggerFactory.getLogger(MainController.class); + + 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; + + public MainController(InitializrMetadataProvider metadataProvider, + GroovyTemplate groovyTemplate, ResourceUrlProvider resourceUrlProvider, + ProjectGenerator projectGenerator, + DependencyMetadataProvider dependencyMetadataProvider) { + super(metadataProvider, resourceUrlProvider); + this.projectGenerator = projectGenerator; + this.dependencyMetadataProvider = dependencyMetadataProvider; + this.commandLineHelpGenerator = new CommandLineHelpGenerator(groovyTemplate); + } + + @ModelAttribute + public BasicProjectRequest projectRequest( + @RequestHeader Map headers) { + ProjectRequest request = new ProjectRequest(); + request.getParameters().putAll(headers); + request.initialize(metadataProvider.get()); + return request; + } + + @RequestMapping(value = "/metadata/config", produces = "application/json") + @ResponseBody + public InitializrMetadata config() { + return metadataProvider.get(); + } + + @RequestMapping(value = "/metadata/client") + public String client() { + return "redirect:/"; + } + + @RequestMapping(value = "/", produces = "text/plain") + public ResponseEntity serviceCapabilitiesText( + @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) { + String appUrl = generateAppUrl(); + InitializrMetadata metadata = metadataProvider.get(); + + BodyBuilder builder = ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN); + if (userAgent != null) { + Agent agent = Agent.fromUserAgent(userAgent); + if (agent != null) { + if (CURL.equals(agent.getId())) { + String content = commandLineHelpGenerator + .generateCurlCapabilities(metadata, appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + if (HTTPIE.equals(agent.getId())) { + String content = commandLineHelpGenerator + .generateHttpieCapabilities(metadata, appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + if (SPRING_BOOT_CLI.equals(agent.getId())) { + String content = commandLineHelpGenerator + .generateSpringBootCliCapabilities(metadata, appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + } + } + String content = commandLineHelpGenerator.generateGenericCapabilities(metadata, + appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + + @RequestMapping(value = "/", produces = "application/hal+json") + public ResponseEntity serviceCapabilitiesHal() { + return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1, + HAL_JSON_CONTENT_TYPE); + } + + @RequestMapping(value = "/", produces = { "application/vnd.initializr.v2.1+json", + "application/json" }) + public ResponseEntity serviceCapabilitiesV21() { + return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1); + } + + @RequestMapping(value = "/", 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(metadataProvider.get(), appUrl); + return ResponseEntity.ok().contentType(contentType).eTag(createUniqueId(content)) + .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(value = "/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 = metadataProvider.get(); + Version v = bootVersion != null ? Version.parse(bootVersion) + : Version.parse(metadata.getBootVersions().getDefault().getId()); + DependencyMetadata dependencyMetadata = 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); + } + + @ModelAttribute("linkTo") + public Mustache.Lambda linkTo() { + return (frag, out) -> { + out.write(this.getLinkTo().apply(frag.execute())); + }; + } + + @RequestMapping(value = "/", produces = "text/html") + public String home(Map model) throws Exception { + renderHome(model); + return "home"; + } + + @RequestMapping("/spring") + public String spring() { + String url = metadataProvider.get().createCliDistributionURl("zip"); + return "redirect:" + url; + } + + @RequestMapping(value = { "/spring.tar.gz", "spring.tgz" }) + public String springTgz() { + String url = metadataProvider.get().createCliDistributionURl("tar.gz"); + return "redirect:" + url; + } + + @RequestMapping("/pom") + @ResponseBody + public ResponseEntity pom(BasicProjectRequest request) { + request.setType("maven-build"); + byte[] mavenPom = projectGenerator.generateMavenPom((ProjectRequest) request); + return createResponseEntity(mavenPom, "application/octet-stream", "pom.xml"); + } + + @RequestMapping("/build") + @ResponseBody + public ResponseEntity gradle(BasicProjectRequest request) { + request.setType("gradle-build"); + byte[] gradleBuild = projectGenerator + .generateGradleBuild((ProjectRequest) request); + return createResponseEntity(gradleBuild, "application/octet-stream", + "build.gradle"); + } + + @RequestMapping("/starter.zip") + @ResponseBody + public ResponseEntity springZip(BasicProjectRequest basicRequest) + throws Exception { + ProjectRequest request = (ProjectRequest) basicRequest; + File dir = projectGenerator.generateProjectStructure(request); + + File download = projectGenerator.createDistributionFile(dir, ".zip"); + + String wrapperScript = getWrapperScript(request); + // TODO: file mode 755 for wrapper script? + + ArchiverFactory.createArchiver("zip").create(download.getName(), + download.getCanonicalFile().getParentFile(), dir); + return upload(download, dir, generateFileName(request, "zip"), "application/zip"); + } + + @RequestMapping(value = "/starter.tgz", produces = "application/x-compress") + @ResponseBody + public ResponseEntity springTgz(BasicProjectRequest basicRequest) + throws Exception { + ProjectRequest request = (ProjectRequest) basicRequest; + File dir = projectGenerator.generateProjectStructure(request); + + File download = projectGenerator.createDistributionFile(dir, ".tar.gz"); + + String wrapperScript = getWrapperScript(request); + // TODO: file mode 755 for wrapper script? + + ArchiverFactory.createArchiver("tar", "gz").create(download.getName(), + download.getCanonicalFile().getParentFile(), dir); + return upload(download, dir, generateFileName(request, "tar.gz"), + "application/x-compress"); + } + + private static String generateFileName(ProjectRequest request, String extension) { + String tmp = request.getArtifactId().replaceAll(" ", "_"); + try { + return URLEncoder.encode(tmp, "UTF-8") + "." + extension; + } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Cannot encode URL", e); + } + } + + private static String getWrapperScript(ProjectRequest request) { + String script = "gradle".equals(request.getBuild()) ? "gradlew" : "mvnw"; + String wrapperScript = request.getBaseDir() != null + ? request.getBaseDir() + "/" + script : script; + return wrapperScript; + } + + private ResponseEntity upload(File download, File dir, String fileName, + String contentType) throws Exception { + byte[] bytes = StreamUtils.copyToByteArray(new FileInputStream(download)); + log.info("Uploading: {} ({} bytes)", download, bytes.length); + ResponseEntity result = createResponseEntity(bytes, contentType, + fileName); + projectGenerator.cleanTempFiles(dir); + return result; + } + + private ResponseEntity createResponseEntity(byte[] content, + String contentType, String fileName) { + String contentDispositionValue = "attachment; filename=\"" + fileName + "\""; + return ResponseEntity.ok().header("Content-Type", contentType) + .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/support/DefaultDependencyMetadataProvider.java b/initializr-web/src/main/java/io/spring/initializr/web/support/DefaultDependencyMetadataProvider.java new file mode 100644 index 00000000..061d5d90 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/support/DefaultDependencyMetadataProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2017 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.support; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.cache.annotation.Cacheable; + +import io.spring.initializr.metadata.BillOfMaterials; +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.DependencyMetadata; +import io.spring.initializr.metadata.DependencyMetadataProvider; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.Repository; +import io.spring.initializr.util.Version; + +/** + * A default {@link DependencyMetadataProvider} implementation. + * + * @author Stephane Nicoll + */ +public class DefaultDependencyMetadataProvider implements DependencyMetadataProvider { + + @Override + @Cacheable(cacheNames = "dependency-metadata", key = "#p1") + public DependencyMetadata get(InitializrMetadata metadata, Version bootVersion) { + Map dependencies = new LinkedHashMap<>(); + for (Dependency d : metadata.getDependencies().getAll()) { + if (d.match(bootVersion)) { + dependencies.put(d.getId(), d.resolve(bootVersion)); + } + } + + Map repositories = new LinkedHashMap<>(); + for (Dependency d : dependencies.values()) { + if (d.getRepository()!=null) { + repositories.put(d.getRepository(), metadata.getConfiguration().getEnv().getRepositories().get(d.getRepository())); + } + } + + Map boms = new LinkedHashMap<>(); + for (Dependency d : dependencies.values()) { + if (d.getBom()!=null) { + boms.put(d.getBom(), metadata.getConfiguration().getEnv().getBoms().get(d.getBom()).resolve(bootVersion)); + } + } + // Each resolved bom may require additional repositories + for (BillOfMaterials b : boms.values()) { + for (String id : b.getRepositories()) { + repositories.put(id, metadata.getConfiguration().getEnv().getRepositories().get(id)); + } + } + + return new DependencyMetadata(bootVersion, dependencies, repositories, boms); + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/support/DefaultInitializrMetadataProvider.java b/initializr-web/src/main/java/io/spring/initializr/web/support/DefaultInitializrMetadataProvider.java new file mode 100644 index 00000000..41edfbdb --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/support/DefaultInitializrMetadataProvider.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2017 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.support; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import io.spring.initializr.metadata.DefaultMetadataElement; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.InitializrMetadataProvider; + +/** + * A default {@link InitializrMetadataProvider} that is able to refresh + * the metadata with the status of the main spring.io site. + * + * @author Stephane Nicoll + */ +public class DefaultInitializrMetadataProvider implements InitializrMetadataProvider { + + private static final Logger log = LoggerFactory + .getLogger(DefaultInitializrMetadataProvider.class); + + private final InitializrMetadata metadata; + private final RestTemplate restTemplate; + + public DefaultInitializrMetadataProvider(InitializrMetadata metadata, RestTemplate restTemplate) { + this.metadata = metadata; + this.restTemplate = restTemplate; + } + + @Override + @Cacheable(value = "initializr", key = "'metadata'") + public InitializrMetadata get() { + updateInitializrMetadata(metadata); + return metadata; + } + + protected void updateInitializrMetadata(InitializrMetadata metadata) { + List bootVersions = fetchBootVersions(); + if (bootVersions!=null && !bootVersions.isEmpty()) { + if (bootVersions.stream().noneMatch(it -> it.isDefault())) { // No default specified + bootVersions.get(0).setDefault(true); + } + metadata.updateSpringBootVersions(bootVersions); + } + } + + protected List fetchBootVersions() { + String url = metadata.getConfiguration().getEnv().getSpringBootMetadataUrl(); + if (StringUtils.hasText(url)) { + try { + log.info("Fetching boot metadata from {}", url); + return new SpringBootMetadataReader(restTemplate, url).getBootVersions(); + } catch (Exception e) { + log.warn("Failed to fetch spring boot metadata", e); + } + } + return null; + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/support/SpringBootMetadataReader.java b/initializr-web/src/main/java/io/spring/initializr/web/support/SpringBootMetadataReader.java new file mode 100644 index 00000000..9236e3de --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/support/SpringBootMetadataReader.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2017 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.support; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import io.spring.initializr.metadata.DefaultMetadataElement; + +/** + * Reads metadata from the main spring.io website. This is a stateful + * service: creates a new instance whenever you need to refresh the + * content. + * + * @author Stephane Nicoll + */ +public class SpringBootMetadataReader { + + private final JSONObject content; + + /** + * Parse the content of the metadata at the specified url + */ + public SpringBootMetadataReader(RestTemplate restTemplate, String url) { + this.content = new JSONObject(restTemplate.getForObject(url, String.class)); + } + + /** + * Return the boot versions parsed by this instance. + */ + public List getBootVersions() { + JSONArray array = content.getJSONArray("projectReleases"); + List list = new ArrayList<>(); + for (int i=0; i dependencies(@RequestParam(required = false) String version) { + List dependencyGroups = metadataProvider.get().getDependencies().getContent(); + List content = new ArrayList<>(); + Version v = StringUtils.isEmpty(version) ? null : Version.parse(version); + dependencyGroups.forEach(g -> { + g.getContent().forEach(d -> { + if (v!=null && d.getVersionRange()!=null) { + if (d.match(v)) { + content.add(new DependencyItem(g.getName(), d)); + } + } else { + content.add(new DependencyItem(g.getName(), d)); + } + }); + }); + String json = writeDependencies(content); + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON). + eTag(createUniqueId(json)).body(json); + } + + private static String writeDependencies(List items) { + JSONObject json = new JSONObject(); + List> maps = new ArrayList<>(); + items.forEach(d -> maps.add(mapDependency(d))); + json.put("dependencies", maps); + return json.toString(); + } + + private static Map mapDependency(DependencyItem item) { + Map result = new HashMap<>(); + Dependency d = item.dependency; + result.put("id", d.getId()); + result.put("name", d.getName()); + result.put("group", item.group); + if (d.getDescription()!=null) { + result.put("description", d.getDescription()); + } + if (d.getWeight()>0) { + result.put("weight", d.getWeight()); + } + if (!CollectionUtils.isEmpty(d.getKeywords()) || !CollectionUtils.isEmpty(d.getAliases())) { + List all = new ArrayList<>(d.getKeywords()); + all.addAll(d.getAliases()); + result.put("keywords", StringUtils.collectionToCommaDelimitedString(all)); + } + return result; + } + + private static class DependencyItem { + private final String group; + private final Dependency dependency; + + DependencyItem(String group, Dependency dependency) { + this.group = group; + this.dependency = dependency; + } + } + + 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/resources/templates/gs.html b/initializr-web/src/main/resources/templates/gs.html index 893001f0..fa7c2f9d 100644 --- a/initializr-web/src/main/resources/templates/gs.html +++ b/initializr-web/src/main/resources/templates/gs.html @@ -39,8 +39,10 @@

Getting Started

-
diff --git a/initializr-web/src/main/resources/templates/home.html b/initializr-web/src/main/resources/templates/home.html index 59dc4969..e8dc3ba1 100644 --- a/initializr-web/src/main/resources/templates/home.html +++ b/initializr-web/src/main/resources/templates/home.html @@ -3,9 +3,9 @@ Spring Initializr - - - + + + @@ -22,23 +22,21 @@

Generate a with Spring Boot

- +
@@ -47,57 +45,54 @@

Artifact coordinates

- - {{groupId.title}} +
- +
@@ -135,22 +130,22 @@
- - - - -<% if (trackingCode) { %> + + + + +{{#trackingCode}} <% } %> + +{{/trackingCode}} diff --git a/initializr-web/src/main/resources/templates/sts-home.html b/initializr-web/src/main/resources/templates/sts-home.html index ece87961..b8ba9623 100644 --- a/initializr-web/src/main/resources/templates/sts-home.html +++ b/initializr-web/src/main/resources/templates/sts-home.html @@ -35,48 +35,55 @@

Spring Initializr

- - - - - + + + + + - <% dependencies.content.each { %> - <% it.content.each { %> + {{#dependencies.content}} + {{#content}} <% } } %> + + {{name}} + + {{/content}} + {{/dependencies.content}} - <% types.content.each { %> + {{#types.content}} <% } %> + + {{name}} + + {{/types.content}} - <% packagings.content.each { %> + {{#packagings.content}} <% } %> + + {{name}} + + {{/packagings.content}} - <% javaVersions.content.each { %> + {{#javaVersions.content}} <% } %> + + {{name}} + + {{/javaVersions.content}} - <% languages.content.each { %> + {{#languages.content}} <% } %> + + {{name}} + + {{/languages.content}} - <% bootVersions.content.each { %> + {{#bootVersions.content}} <% } %> + + {{name}} + + {{/bootVersions.content}}
diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/AbstractInitializrIntegrationTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/AbstractInitializrIntegrationTests.groovy deleted file mode 100644 index fe715ed2..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/AbstractInitializrIntegrationTests.groovy +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2012-2016 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.nio.charset.Charset - -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.mapper.InitializrMetadataVersion -import io.spring.initializr.web.support.DefaultInitializrMetadataProvider -import org.json.JSONObject -import org.junit.Before -import org.junit.Rule -import org.junit.rules.TemporaryFolder -import org.junit.runner.RunWith -import org.skyscreamer.jsonassert.JSONAssert -import org.skyscreamer.jsonassert.JSONCompareMode - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.web.client.RestTemplateBuilder -import org.springframework.context.annotation.Bean -import org.springframework.core.io.ClassPathResource -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.test.context.junit4.SpringRunner -import org.springframework.util.StreamUtils -import org.springframework.web.client.RestTemplate - -import static org.junit.Assert.assertTrue - -/** - * @author Stephane Nicoll - */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = Config.class) -abstract class AbstractInitializrIntegrationTests { - - static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1.mediaType - - @Rule - public final TemporaryFolder folder = new TemporaryFolder() - - @Autowired - private RestTemplateBuilder restTemplateBuilder - - RestTemplate restTemplate - - @Before - void before() { - restTemplate = restTemplateBuilder.build() - } - - abstract String createUrl(String context) - - String htmlHome() { - def headers = new HttpHeaders() - headers.setAccept([MediaType.TEXT_HTML]) - restTemplate.exchange(createUrl('/'), HttpMethod.GET, new HttpEntity(headers), String).body - } - - /** - * Validate the 'Content-Type' header of the specified response. - */ - protected void validateContentType(ResponseEntity response, MediaType expected) { - def actual = response.headers.getContentType() - assertTrue "Non compatible media-type, expected $expected, got $actual", - actual.isCompatibleWith(expected) - } - - - protected void validateMetadata(ResponseEntity response, MediaType mediaType, - String version, JSONCompareMode compareMode) { - validateContentType(response, mediaType) - def json = new JSONObject(response.body) - def expected = readMetadataJson(version) - JSONAssert.assertEquals(expected, json, compareMode) - } - - protected void validateCurrentMetadata(ResponseEntity response) { - validateContentType(response, CURRENT_METADATA_MEDIA_TYPE) - validateCurrentMetadata(new JSONObject(response.body)) - } - - protected void validateCurrentMetadata(JSONObject json) { - def expected = readMetadataJson('2.1.0') - JSONAssert.assertEquals(expected, json, JSONCompareMode.STRICT) - } - - private JSONObject readMetadataJson(String version) { - readJsonFrom("metadata/test-default-$version" + ".json") - } - - /** - * Return a {@link ProjectAssert} for the following archive content. - */ - protected ProjectAssert zipProjectAssert(byte[] content) { - projectAssert(content, ArchiveType.ZIP) - } - - /** - * Return a {@link ProjectAssert} for the following TGZ archive. - */ - protected ProjectAssert tgzProjectAssert(byte[] content) { - projectAssert(content, ArchiveType.TGZ) - } - - protected ProjectAssert downloadZip(String context) { - def body = downloadArchive(context) - zipProjectAssert(body) - } - - protected ProjectAssert downloadTgz(String context) { - def body = downloadArchive(context) - tgzProjectAssert(body) - } - - protected byte[] downloadArchive(String context) { - restTemplate.getForObject(createUrl(context), byte[]) - } - - protected ResponseEntity invokeHome(String userAgentHeader, String... acceptHeaders) { - execute('/', String, userAgentHeader, acceptHeaders) - } - - protected ResponseEntity execute(String contextPath, Class responseType, - String userAgentHeader, String... acceptHeaders) { - HttpHeaders headers = new HttpHeaders(); - if (userAgentHeader) { - headers.set("User-Agent", userAgentHeader); - } - if (acceptHeaders) { - List mediaTypes = new ArrayList<>() - for (String acceptHeader : acceptHeaders) { - mediaTypes.add(MediaType.parseMediaType(acceptHeader)) - } - headers.setAccept(mediaTypes) - } else { - headers.setAccept(Collections.emptyList()) - } - return restTemplate.exchange(createUrl(contextPath), - HttpMethod.GET, new HttpEntity(headers), responseType) - } - - protected ProjectAssert projectAssert(byte[] content, ArchiveType archiveType) { - def archiveFile = writeArchive(content) - - def project = folder.newFolder() - switch (archiveType) { - case ArchiveType.ZIP: - new AntBuilder().unzip(dest: project, src: archiveFile) - break - case ArchiveType.TGZ: - new AntBuilder().untar(dest: project, src: archiveFile, compression: 'gzip') - break - } - new ProjectAssert(project) - } - - protected File writeArchive(byte[] body) { - def archiveFile = folder.newFile() - def stream = new FileOutputStream(archiveFile) - try { - stream.write(body) - } finally { - stream.close() - } - archiveFile - } - - protected JSONObject readJsonFrom(String path) { - def resource = new ClassPathResource(path) - def stream = resource.inputStream - try { - def json = StreamUtils.copyToString(stream, Charset.forName('UTF-8')) - String placeholder = "" - if (this.hasProperty("host")) { - placeholder = "$host" - } - if (this.hasProperty("port")) { - placeholder = "$host:$port" - } - // Let's parse the port as it is random - // TODO: put the port back somehow so it appears in stubs - def content = json.replaceAll('@host@', placeholder) - new JSONObject(content) - } finally { - stream.close() - } - } - - - private enum ArchiveType { - ZIP, - - TGZ - } - - @EnableAutoConfiguration - static class Config { - - @Bean - InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties) { - new DefaultInitializrMetadataProvider( - InitializrMetadataBuilder.fromInitializrProperties(properties).build(), - new RestTemplate()) { - @Override - protected void updateInitializrMetadata(InitializrMetadata metadata) { - null // Disable metadata fetching from spring.io - } - } - } - - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessorTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessorTests.groovy deleted file mode 100644 index 5448ee78..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessorTests.groovy +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2012-2016 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.autoconfigure - -import org.junit.Test - -import org.springframework.boot.SpringApplication -import org.springframework.mock.env.MockEnvironment - -import static org.hamcrest.CoreMatchers.nullValue -import static org.junit.Assert.assertThat -import static org.hamcrest.CoreMatchers.is - -/** - * @author Stephane Nicoll - */ -class CloudfoundryEnvironmentPostProcessorTests { - - private final CloudfoundryEnvironmentPostProcessor postProcessor = new CloudfoundryEnvironmentPostProcessor() - private final MockEnvironment environment = new MockEnvironment(); - private final SpringApplication application = new SpringApplication() - - @Test - void parseCredentials() { - environment.setProperty('vcap.services.stats-index.credentials.uri', - 'http://user:pass@example.com/bar/biz?param=one') - postProcessor.postProcessEnvironment(environment, application) - - assertThat(environment.getProperty('initializr.stats.elastic.uri'), - is('http://example.com/bar/biz?param=one')) - assertThat(environment.getProperty('initializr.stats.elastic.username'), is('user')) - assertThat(environment.getProperty('initializr.stats.elastic.password'), is('pass')) - } - - @Test - void parseNoCredentials() { - environment.setProperty('vcap.services.stats-index.credentials.uri', - 'http://example.com/bar/biz?param=one') - postProcessor.postProcessEnvironment(environment, application) - - assertThat(environment.getProperty('initializr.stats.elastic.uri'), - is('http://example.com/bar/biz?param=one')) - assertThat(environment.getProperty('initializr.stats.elastic.username'), is(nullValue())) - assertThat(environment.getProperty('initializr.stats.elastic.password'), is(nullValue())) - } - - @Test - void parseNoVcapUri() { - postProcessor.postProcessEnvironment(environment, application) - - assertThat(environment.getProperty('initializr.stats.elastic.uri'), is(nullValue())) - assertThat(environment.getProperty('initializr.stats.elastic.username'), is(nullValue())) - assertThat(environment.getProperty('initializr.stats.elastic.password'), is(nullValue())) - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/DependencyMetadataJsonMapperTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/DependencyMetadataJsonMapperTests.groovy deleted file mode 100644 index 216d8ad3..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/DependencyMetadataJsonMapperTests.groovy +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-2015 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.mapper - -import groovy.json.JsonSlurper -import io.spring.initializr.metadata.BillOfMaterials -import io.spring.initializr.metadata.Dependency -import io.spring.initializr.metadata.DependencyMetadata -import io.spring.initializr.metadata.Repository -import io.spring.initializr.util.Version -import org.junit.Test - -import static org.junit.Assert.* - -/** - * @author Stephane Nicoll - */ -class DependencyMetadataJsonMapperTests { - - private final DependencyMetadataJsonMapper mapper = new DependencyMetadataV21JsonMapper() - private final JsonSlurper slurper = new JsonSlurper() - - @Test - void mapDependency() { - Dependency d = new Dependency(id: 'foo', groupId: 'org.foo', artifactId: 'foo', - repository: 'my-repo', bom: 'my-bom') - Repository repository = new Repository(name: 'foo-repo', - url: new URL('http://example.com/foo')) - BillOfMaterials bom = new BillOfMaterials('groupId': 'org.foo', - artifactId: 'foo-bom', version: '1.0.0.RELEASE') - DependencyMetadata metadata = new DependencyMetadata(Version.parse('1.2.0.RELEASE'), - [(d.id): d], ['repo-id': repository], ['bom-id': bom]) - def content = slurper.parseText(mapper.write(metadata)) - println content - assertEquals 'my-bom', content.dependencies['foo'].bom - assertEquals 'my-repo', content.dependencies['foo'].repository - assertEquals 'foo-repo', content.repositories['repo-id'].name - assertEquals 'foo-bom', content.boms['bom-id'].artifactId - assertEquals '1.0.0.RELEASE', content.boms['bom-id'].version - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.groovy deleted file mode 100644 index 8e9eaba4..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.groovy +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2012-2017 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.mapper - -import groovy.json.JsonSlurper -import io.spring.initializr.metadata.Dependency -import io.spring.initializr.metadata.InitializrMetadata -import io.spring.initializr.metadata.Link -import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder -import org.junit.Test - -import static org.junit.Assert.assertEquals - -/** - * @author Stephane Nicoll - */ -class InitializrMetadataJsonMapperTests { - - private final InitializrMetadataJsonMapper jsonMapper = new InitializrMetadataV21JsonMapper() - private final JsonSlurper slurper = new JsonSlurper() - - @Test - void withNoAppUrl() { - InitializrMetadata metadata = new InitializrMetadataTestBuilder().addType('foo', true, '/foo.zip', 'none', 'test') - .addDependencyGroup('foo', 'one', 'two').build() - def json = jsonMapper.write(metadata, null) - def result = slurper.parseText(json) - assertEquals '/foo.zip?type=foo{&dependencies,packaging,javaVersion,language,bootVersion,' + - 'groupId,artifactId,version,name,description,packageName}', result._links.foo.href - } - - @Test - void withAppUrl() { - InitializrMetadata metadata = new InitializrMetadataTestBuilder().addType('foo', true, '/foo.zip', 'none', 'test') - .addDependencyGroup('foo', 'one', 'two').build() - def json = jsonMapper.write(metadata, 'http://server:8080/my-app') - def result = slurper.parseText(json) - assertEquals 'http://server:8080/my-app/foo.zip?type=foo{&dependencies,packaging,javaVersion,' + - 'language,bootVersion,groupId,artifactId,version,name,description,packageName}', - result._links.foo.href - } - - @Test - void keepLinksOrdering() { - def dependency = new Dependency(id: 'foo') - dependency.links << new Link(rel: 'guide', href: 'https://example.com/how-to') - dependency.links << new Link(rel: 'reference', href: 'https://example.com/doc') - InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() - .addDependencyGroup('test', dependency).build() - def json = jsonMapper.write(metadata, null) - def first = json.indexOf('https://example.com/how-to') - def second = json.indexOf('https://example.com/doc') - assert first < second - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/LinkMapperTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/LinkMapperTests.groovy deleted file mode 100644 index f57ed38d..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/mapper/LinkMapperTests.groovy +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2012-2017 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.mapper - -import io.spring.initializr.metadata.Link -import org.junit.Test - -/** - * Tests for {@link LinkMapper}. - * - * @author Stephane Nicoll - */ -class LinkMapperTests { - - @Test - void mapSimpleRel() { - def links = new ArrayList() - links << new Link(rel: 'a', 'href': 'https://example.com', - description: 'some description') - def model = LinkMapper.mapLinks(links) - assert model.size() == 1 - assert model.containsKey('a') - def linkModel = model['a'] - assert linkModel.size() == 2 - assert linkModel['href'] == 'https://example.com' - assert linkModel['title'] == 'some description' - } - - @Test - void mapTemplatedRel() { - def links = new ArrayList() - links << new Link(rel: 'a', 'href': 'https://example.com/{bootVersion}/a', - templated: true) - def model = LinkMapper.mapLinks(links) - assert model.size() == 1 - assert model.containsKey('a') - def linkModel = model['a'] - assert linkModel.size() == 2 - assert linkModel['href'] == 'https://example.com/{bootVersion}/a' - assert linkModel['templated'] == true - } - - @Test - void mergeSeveralLinksInArray() { - def links = new ArrayList() - links << new Link(rel: 'a', 'href': 'https://example.com', - description: 'some description') - links << new Link(rel: 'a', 'href': 'https://example.com/2') - def model = LinkMapper.mapLinks(links) - assert model.size() == 1 - assert model.containsKey('a') - def linksModel = model['a'] - assert linksModel.size() == 2 - assert linksModel[0]['href'] == 'https://example.com' - assert linksModel[1]['href'] == 'https://example.com/2' - } - - @Test - void keepOrdering() { - def links = new ArrayList() - links << new Link(rel: 'a', 'href': 'https://example.com') - links << new Link(rel: 'b', 'href': 'https://example.com') - def model = LinkMapper.mapLinks(links) - def iterator = model.keySet().iterator() - assert ++iterator == 'a' - assert ++iterator == 'b' - } - - @Test - void keepOrderingWithMultipleUrlForSameRel() { - def links = new ArrayList() - links << new Link(rel: 'a', 'href': 'https://example.com') - links << new Link(rel: 'b', 'href': 'https://example.com') - links << new Link(rel: 'a', 'href': 'https://example.com') - def model = LinkMapper.mapLinks(links) - def iterator = model.keySet().iterator() - assert ++iterator == 'a' - assert ++iterator == 'b' - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerDefaultsIntegrationTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerDefaultsIntegrationTests.groovy deleted file mode 100644 index 06c716fd..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerDefaultsIntegrationTests.groovy +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2016 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.test.generator.PomAssert -import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests -import org.junit.Test - -import org.springframework.test.context.ActiveProfiles - -import static org.junit.Assert.assertTrue - -/** - * @author Stephane Nicoll - */ -@ActiveProfiles(['test-default', 'test-custom-defaults']) -class MainControllerDefaultsIntegrationTests extends AbstractInitializrControllerIntegrationTests { - - // see defaults customization - - @Test - void generateDefaultPom() { - def content = restTemplate.getForObject(createUrl('/pom.xml?style=web'), String) - def pomAssert = new PomAssert(content) - pomAssert.hasGroupId('org.foo').hasArtifactId('foo-bar').hasVersion('1.2.4-SNAPSHOT').hasPackaging('jar') - .hasName('FooBar').hasDescription('FooBar Project') - } - - @Test - void defaultsAppliedToHome() { - def body = htmlHome() - assertTrue 'custom groupId not found', body.contains('org.foo') - assertTrue 'custom artifactId not found', body.contains('foo-bar') - assertTrue 'custom name not found', body.contains('FooBar') - assertTrue 'custom description not found', body.contains('FooBar Project') - assertTrue 'custom package not found', body.contains('org.foo.demo') - } - -} \ No newline at end of file diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerDependenciesTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerDependenciesTests.groovy deleted file mode 100644 index 3d89b601..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerDependenciesTests.groovy +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2012-2016 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.web.AbstractInitializrControllerIntegrationTests -import org.json.JSONObject -import org.junit.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.hamcrest.CoreMatchers.nullValue -import static org.hamcrest.core.IsNot.not -import static org.junit.Assert.assertThat - -/** - * @author Stephane Nicoll - */ -@ActiveProfiles('test-default') -class MainControllerDependenciesTests extends AbstractInitializrControllerIntegrationTests { - - @Test - void noBootVersion() { - ResponseEntity response = execute('/dependencies', String, null, 'application/json') - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())) - validateContentType(response, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE) - validateDependenciesOutput('1.1.4', new JSONObject(response.body)) - } - - @Test - void filteredDependencies() { - ResponseEntity response = execute('/dependencies?bootVersion=1.2.1.RELEASE', - String, null, 'application/json') - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())) - validateContentType(response, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE) - validateDependenciesOutput('1.2.1', new JSONObject(response.body)) - } - - protected void validateDependenciesOutput(String version, JSONObject actual) { - def expected = readJsonFrom("metadata/dependencies/test-dependencies-$version" + ".json") - JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT) - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerEnvIntegrationTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerEnvIntegrationTests.groovy deleted file mode 100644 index 7e3d7e73..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerEnvIntegrationTests.groovy +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2012-2016 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.test.generator.ProjectAssert -import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests -import org.junit.Test - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.test.context.ActiveProfiles - -import static org.junit.Assert.* - -/** - * @author Stephane Nicoll - */ -@ActiveProfiles(['test-default', 'test-custom-env']) -class MainControllerEnvIntegrationTests extends AbstractInitializrControllerIntegrationTests { - - @Test - void downloadCliWithCustomRepository() { - def entity = restTemplate.getForEntity(createUrl('/spring'), HttpEntity.class) - assertEquals HttpStatus.FOUND, entity.getStatusCode() - def expected = "https://repo.spring.io/lib-release/org/springframework/boot/spring-boot-cli/1.1.4.RELEASE/spring-boot-cli-1.1.4.RELEASE-bin.zip" - assertEquals new URI(expected), entity.getHeaders().getLocation() - } - - @Test - void doNotForceSsl() { - ResponseEntity response = invokeHome('curl/1.2.4', "*/*") - String body = response.getBody() - assertTrue "Must not force https", body.contains("http://start.spring.io/") - assertFalse "Must not force https", body.contains('https://') - } - - @Test - void generateProjectWithInvalidName() { - downloadZip('/starter.zip?style=data-jpa&name=Invalid') - .isJavaProject(ProjectAssert.DEFAULT_PACKAGE_NAME, 'FooBarApplication') - .isMavenProject() - .hasStaticAndTemplatesResources(false).pomAssert() - .hasDependenciesCount(2) - .hasSpringBootStarterDependency('data-jpa') - .hasSpringBootStarterTest() - } - - @Test - void googleAnalytics() { - def body = htmlHome() - assertTrue 'google analytics should be enabled', body.contains("ga('create', 'UA-1234567-89', 'auto');") - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerIntegrationTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerIntegrationTests.groovy deleted file mode 100644 index 4572b5ed..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerIntegrationTests.groovy +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright 2012-2016 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 groovy.json.JsonSlurper -import io.spring.initializr.metadata.Dependency -import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests -import io.spring.initializr.web.mapper.InitializrMetadataVersion -import org.json.JSONObject -import org.junit.Ignore -import org.junit.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 -import org.springframework.test.context.ActiveProfiles -import org.springframework.web.client.HttpClientErrorException - -import static org.hamcrest.CoreMatchers.allOf -import static org.hamcrest.CoreMatchers.containsString -import static org.hamcrest.CoreMatchers.nullValue -import static org.hamcrest.core.IsNot.not -import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertFalse -import static org.junit.Assert.assertNotNull -import static org.junit.Assert.assertThat -import static org.junit.Assert.assertTrue -import static org.junit.Assert.fail - -/** - * @author Stephane Nicoll - */ -@ActiveProfiles('test-default') -class MainControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests { - - @Test - void simpleZipProject() { - downloadZip('/starter.zip?style=web&style=jpa').isJavaProject() - .hasFile('.gitignore') - .isMavenProject() - .hasStaticAndTemplatesResources(true).pomAssert() - .hasDependenciesCount(3) - .hasSpringBootStarterDependency('web') - .hasSpringBootStarterDependency('data-jpa') // alias jpa -> data-jpa - .hasSpringBootStarterTest() - } - - @Test - void simpleTgzProject() { - downloadTgz('/starter.tgz?style=org.acme:foo').isJavaProject() - .hasFile('.gitignore') - .isMavenProject() - .hasStaticAndTemplatesResources(false).pomAssert() - .hasDependenciesCount(2) - .hasDependency('org.acme', 'foo', '1.3.5') - } - - @Test - void dependencyInRange() { - def biz = new Dependency(id: 'biz', groupId: 'org.acme', - artifactId: 'biz', version: '1.3.5', scope: 'runtime') - downloadTgz('/starter.tgz?style=org.acme:biz&bootVersion=1.2.1.RELEASE').isJavaProject().isMavenProject() - .hasStaticAndTemplatesResources(false).pomAssert() - .hasDependenciesCount(2) - .hasDependency(biz) - } - - @Test - void dependencyNotInRange() { - try { - execute('/starter.tgz?style=org.acme:bur', byte[], null, null) - } catch (HttpClientErrorException ex) { - assertEquals HttpStatus.NOT_ACCEPTABLE, ex.statusCode - } - } - - @Test - void noDependencyProject() { - downloadZip('/starter.zip').isJavaProject().isMavenProject() - .hasStaticAndTemplatesResources(false).pomAssert() - .hasDependenciesCount(2) - .hasSpringBootStarterRootDependency() // the root dep is added if none is specified - .hasSpringBootStarterTest() - } - - @Test - void dependenciesIsAnAliasOfStyle() { - downloadZip('/starter.zip?dependencies=web&dependencies=jpa').isJavaProject().isMavenProject() - .hasStaticAndTemplatesResources(true).pomAssert() - .hasDependenciesCount(3) - .hasSpringBootStarterDependency('web') - .hasSpringBootStarterDependency('data-jpa') // alias jpa -> data-jpa - .hasSpringBootStarterTest() - } - - @Test - void dependenciesIsAnAliasOfStyleCommaSeparated() { - downloadZip('/starter.zip?dependencies=web,jpa').isJavaProject().isMavenProject() - .hasStaticAndTemplatesResources(true).pomAssert() - .hasDependenciesCount(3) - .hasSpringBootStarterDependency('web') - .hasSpringBootStarterDependency('data-jpa') // alias jpa -> data-jpa - .hasSpringBootStarterTest() - } - - @Test - void gradleWarProject() { - downloadZip('/starter.zip?style=web&style=security&packaging=war&type=gradle.zip') - .isJavaWarProject().isGradleProject() - } - - @Test - void downloadCli() { - assertSpringCliRedirect('/spring', 'zip') - } - - @Test - void downloadCliAsZip() { - assertSpringCliRedirect('/spring.zip', 'zip') - } - - @Test - void downloadCliAsTarGz() { - assertSpringCliRedirect('/spring.tar.gz', 'tar.gz') - } - - @Test - void downloadCliAsTgz() { - assertSpringCliRedirect('/spring.tgz', 'tar.gz') - } - - private void assertSpringCliRedirect(String context, String extension) { - def entity = restTemplate.getForEntity(createUrl(context), ResponseEntity.class) - assertEquals HttpStatus.FOUND, entity.getStatusCode() - def expected = "https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.1.4.RELEASE/spring-boot-cli-1.1.4.RELEASE-bin.$extension" - assertEquals new URI(expected), entity.getHeaders().getLocation() - } - - @Test - void metadataWithNoAcceptHeader() { // rest template sets application/json by default - ResponseEntity response = invokeHome(null, '*/*') - validateCurrentMetadata(response) - } - - @Test - @Ignore("Need a comparator that does not care about the number of elements in an array") - void currentMetadataCompatibleWithV2() { - ResponseEntity response = invokeHome(null, '*/*') - validateMetadata(response, 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.mediaType, '2.0.0', JSONCompareMode.STRICT) - } - - @Test - void metadataWithCurrentAcceptHeader() { - requests.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), not(nullValue())) - validateContentType(response, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE) - validateCurrentMetadata(new JSONObject(response.body)) - } - - @Test - void metadataWithSeveralAcceptHeader() { - ResponseEntity response = invokeHome(null, - 'application/vnd.initializr.v2.1+json', 'application/vnd.initializr.v2+json') - validateContentType(response, CURRENT_METADATA_MEDIA_TYPE) - validateCurrentMetadata(new JSONObject(response.body)) - } - - @Test - void metadataWithHalAcceptHeader() { - ResponseEntity response = invokeHome(null, 'application/hal+json') - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())) - validateContentType(response, MainController.HAL_JSON_CONTENT_TYPE) - validateCurrentMetadata(new JSONObject(response.body)) - } - - @Test - void metadataWithUnknownAcceptHeader() { - try { - invokeHome(null, 'application/vnd.initializr.v5.4+json') - } catch (HttpClientErrorException ex) { - assertEquals HttpStatus.NOT_ACCEPTABLE, ex.statusCode - } - } - - @Test - void curlReceivesTextByDefault() { - ResponseEntity response = invokeHome('curl/1.2.4', "*/*") - validateCurlHelpContent(response) - } - - @Test - void curlCanStillDownloadZipArchive() { - ResponseEntity response = execute('/starter.zip', byte[], 'curl/1.2.4', "*/*") - zipProjectAssert(response.body).isMavenProject().isJavaProject() - } - - @Test - void curlCanStillDownloadTgzArchive() { - ResponseEntity response = execute('/starter.tgz', byte[], 'curl/1.2.4', "*/*") - tgzProjectAssert(response.body).isMavenProject().isJavaProject() - } - - @Test - // make sure curl can still receive metadata with json - void curlWithAcceptHeaderJson() { - ResponseEntity response = invokeHome('curl/1.2.4', "application/json") - validateContentType(response, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE) - validateCurrentMetadata(new JSONObject(response.body)) - } - - @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, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE) - validateCurrentMetadata(new JSONObject(response.body)) - } - - @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, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE) - validateCurrentMetadata(new JSONObject(response.body)) - } - - @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() { - def json = getMetadataJson() - validateCurrentMetadata(json) - } - - - private void validateCurlHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN) - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())) - assertThat(response.body, allOf( - containsString("Spring Initializr"), - containsString('Examples:'), - containsString("curl"))) - } - - private void validateHttpIeHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN) - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())) - assertThat(response.body, allOf( - containsString("Spring Initializr"), - containsString('Examples:'), - not(containsString("curl")), - containsString("http"))) - } - - private void validateGenericHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN) - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())) - assertThat(response.body, allOf( - containsString("Spring Initializr"), - not(containsString('Examples:')), - not(containsString("curl")))) - } - - private void validateSpringBootHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN) - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())) - assertThat(response.body, allOf( - containsString("Service capabilities"), - containsString("Supported dependencies"), - not(containsString('Examples:')), - not(containsString("curl")))) - } - - @Test - void missingDependencyProperException() { - try { - downloadArchive('/starter.zip?style=foo:bar') - fail("Should have failed") - } catch (HttpClientErrorException ex) { - assertEquals HttpStatus.BAD_REQUEST, ex.getStatusCode() - assertStandardErrorBody(ex.getResponseBodyAsString(), - "Unknown dependency 'foo:bar' check project metadata") - } - } - - @Test - void invalidDependencyProperException() { - try { - downloadArchive('/starter.zip?style=foo') - fail("Should have failed") - } catch (HttpClientErrorException ex) { - assertEquals HttpStatus.BAD_REQUEST, ex.getStatusCode() - assertStandardErrorBody(ex.getResponseBodyAsString(), - "Unknown dependency 'foo' check project metadata") - } - } - - @Test - void homeIsForm() { - def body = htmlHome() - assertTrue "Wrong body:\n$body", body.contains('action="/starter.zip"') - } - - @Test - void homeIsJson() { - def body = invokeHome(null, null).body - assertTrue("Wrong body:\n$body", body.contains('"dependencies"')) - } - - @Test - void webIsAddedPom() { - def body = restTemplate.getForObject(createUrl('/pom.xml?packaging=war'), String) - assertTrue("Wrong body:\n$body", body.contains('spring-boot-starter-web')) - assertTrue("Wrong body:\n$body", body.contains('provided')) - } - - @Test - void webIsAddedGradle() { - def body = restTemplate.getForObject(createUrl('/build.gradle?packaging=war'), String) - assertTrue("Wrong body:\n$body", body.contains('spring-boot-starter-web')) - assertTrue("Wrong body:\n$body", body.contains('providedRuntime')) - } - - @Test - void homeHasWebStyle() { - def body = htmlHome() - assertTrue("Wrong body:\n$body", body.contains('name="style" value="web"')) - } - - @Test - void homeHasBootVersion() { - def body = htmlHome() - assertTrue("Wrong body:\n$body", body.contains('name="bootVersion"')) - assertTrue("Wrong body:\n$body", body.contains('1.2.0.BUILD-SNAPSHOT"')) - } - - @Test - void homeHasOnlyProjectFormatTypes() { - def body = htmlHome() - assertTrue 'maven project not found', body.contains('Maven Project') - assertFalse 'maven pom type should have been filtered', body.contains('Maven POM') - } - - @Test - void downloadStarter() { - def body = restTemplate.getForObject(createUrl('starter.zip'), byte[]) - assertNotNull(body) - assertTrue(body.length > 100) - } - - @Test - void installer() { - def response = restTemplate.getForEntity(createUrl('install.sh'), String) - assertEquals(HttpStatus.OK, response.getStatusCode()) - assertNotNull(response.body) - } - - @Test - void googleAnalyticsDisabledByDefault() { - def body = htmlHome() - assertFalse 'google analytics should be disabled', body.contains('GoogleAnalyticsObject') - } - - private JSONObject getMetadataJson() { - getMetadataJson(null, null) - } - - private JSONObject getMetadataJson(String userAgentHeader, String acceptHeader) { - String json = invokeHome(userAgentHeader, acceptHeader).body - return new JSONObject(json) - } - - private static void assertStandardErrorBody(String body, String message) { - assertNotNull "error body must be available", body - def model = new JsonSlurper().parseText(body) - assertEquals message, model.message - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerServiceMetadataIntegrationTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerServiceMetadataIntegrationTests.groovy deleted file mode 100644 index 2485d16a..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/MainControllerServiceMetadataIntegrationTests.groovy +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2012-2016 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 -import io.spring.initializr.metadata.InitializrMetadataBuilder -import io.spring.initializr.metadata.InitializrMetadataProvider -import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests -import org.json.JSONObject -import org.junit.Test -import org.skyscreamer.jsonassert.JSONAssert -import org.skyscreamer.jsonassert.JSONCompareMode - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.core.io.UrlResource -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.test.context.ActiveProfiles -import org.springframework.web.client.HttpClientErrorException - -import static org.junit.Assert.assertEquals - -/** - * @author Stephane Nicoll - */ -@ActiveProfiles('test-default') -class MainControllerServiceMetadataIntegrationTests - extends AbstractFullStackInitializrIntegrationTests { - - @Autowired - private InitializrMetadataProvider metadataProvider - - @Test - void initializeRemoteConfig() { - InitializrMetadata localMetadata = metadataProvider.get() - InitializrMetadata metadata = InitializrMetadataBuilder.create().withInitializrMetadata( - new UrlResource(createUrl('/metadata/config'))).build() - // Basic assertions - assertEquals(localMetadata.dependencies.content.size(), metadata.dependencies.content.size()) - assertEquals(localMetadata.types.content.size(), metadata.types.content.size()) - assertEquals(localMetadata.bootVersions.content.size(), metadata.bootVersions.content.size()) - assertEquals(localMetadata.packagings.content.size(), metadata.packagings.content.size()) - assertEquals(localMetadata.javaVersions.content.size(), metadata.javaVersions.content.size()) - assertEquals(localMetadata.languages.content.size(), metadata.languages.content.size()) - } - - @Test - void textPlainNotAccepted() { - try { - execute('/metadata/config', String, null, 'text/plain') - } catch (HttpClientErrorException ex) { - assertEquals HttpStatus.NOT_ACCEPTABLE, ex.statusCode - } - } - - @Test - void validateJson() { - ResponseEntity response = execute('/metadata/config', String, null, 'application/json') - validateContentType(response, MediaType.APPLICATION_JSON) - JSONObject json = new JSONObject(response.body) - def expected = readJsonFrom("metadata/config/test-default.json") - JSONAssert.assertEquals(expected, json, JSONCompareMode.STRICT) - } - - @Test - void metadataClientRedirect() { - ResponseEntity response = execute('/metadata/client', String, null, 'application/json') - validateCurrentMetadata(response) - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/ProjectGenerationPostProcessorTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/ProjectGenerationPostProcessorTests.groovy deleted file mode 100644 index ffbb7855..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/ProjectGenerationPostProcessorTests.groovy +++ /dev/null @@ -1,59 +0,0 @@ -package io.spring.initializr.web.project - -import io.spring.initializr.generator.ProjectRequest -import io.spring.initializr.generator.ProjectRequestPostProcessor -import io.spring.initializr.generator.ProjectRequestPostProcessorAdapter -import io.spring.initializr.metadata.InitializrMetadata -import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests -import org.junit.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 ProjectGenerationPostProcessorTests extends AbstractInitializrControllerIntegrationTests { - - - @Test - void postProcessorsInvoked() { - downloadZip('/starter.zip?bootVersion=1.2.4.RELEASE&javaVersion=1.6') - .isJavaProject() - .isMavenProject().pomAssert() - .hasSpringBootParent('1.2.3.RELEASE') - .hasProperty('java.version', '1.7') - } - - - @Configuration - static class ProjectRequestPostProcessorConfiguration { - - @Bean - @Order(2) - ProjectRequestPostProcessor secondPostProcessor() { - new ProjectRequestPostProcessorAdapter() { - @Override - void postProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata) { - request.javaVersion = '1.7' - } - } - } - - @Bean - @Order(1) - ProjectRequestPostProcessor firstPostProcessor() { - new ProjectRequestPostProcessorAdapter() { - @Override - void postProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata) { - request.javaVersion = '1.2' - request.bootVersion = '1.2.3.RELEASE' - } - } - } - - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/ProjectGenerationSmokeTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/ProjectGenerationSmokeTests.groovy deleted file mode 100644 index 255ffb8b..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/ProjectGenerationSmokeTests.groovy +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright 2012-2017 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 geb.Browser -import io.spring.initializr.test.generator.ProjectAssert -import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests -import io.spring.initializr.web.project.test.HomePage -import org.junit.After -import org.junit.Assume -import org.junit.Before -import org.junit.Test -import org.openqa.selenium.Keys -import org.openqa.selenium.WebDriver -import org.openqa.selenium.firefox.FirefoxDriver -import org.openqa.selenium.firefox.FirefoxProfile -import org.openqa.selenium.interactions.Actions - -import org.springframework.test.context.ActiveProfiles - -import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertTrue - -/** - * - * @author Stephane Nicoll - */ -@ActiveProfiles('test-default') -class ProjectGenerationSmokeTests - extends AbstractFullStackInitializrIntegrationTests { - - private File downloadDir - private WebDriver driver - private Browser browser - private Actions actions - - private def enterAction - - @Before - void setup() { - Assume.assumeTrue("Smoke tests disabled (set System property 'smoke.test')", - Boolean.getBoolean("smoke.test")) - downloadDir = folder.newFolder() - FirefoxProfile fxProfile = new FirefoxProfile(); - - fxProfile.setPreference("browser.download.folderList", 2); - fxProfile.setPreference("browser.download.manager.showWhenStarting", false); - fxProfile.setPreference("browser.download.dir", downloadDir.getAbsolutePath()); - fxProfile.setPreference("browser.helperApps.neverAsk.saveToDisk", - "application/zip,application/x-compress,application/octet-stream"); - - driver = new FirefoxDriver(fxProfile); - actions = new Actions(driver) - browser = new Browser() - browser.driver = driver - - enterAction = actions.sendKeys(Keys.ENTER).build() - } - - @After - void destroy() { - if (driver != null) { - driver.close(); - } - } - - @Test - void createSimpleProject() { - toHome { - page.generateProject.click() - at HomePage - assertSimpleProject() - .isMavenProject() - .pomAssert() - .hasDependenciesCount(2) - .hasSpringBootStarterRootDependency() - .hasSpringBootStarterTest() - } - } - - @Test - void createSimpleProjectWithGradle() { - toHome { - page.type = 'gradle-project' - page.generateProject.click() - at HomePage - assertSimpleProject() - .isGradleProject() - .gradleBuildAssert() - .contains("compile('org.springframework.boot:spring-boot-starter')") - .contains("testCompile('org.springframework.boot:spring-boot-starter-test')") - } - } - - @Test - void createSimpleProjectWithDifferentBootVersion() { - toHome { - page.bootVersion = '1.0.2.RELEASE' - page.generateProject.click() - at HomePage - assertSimpleProject() - .isMavenProject() - .pomAssert() - .hasSpringBootParent('1.0.2.RELEASE') - .hasDependenciesCount(2) - .hasSpringBootStarterRootDependency() - .hasSpringBootStarterTest() - - } - } - - @Test - void createSimpleProjectWithDependencies() { - toHome { - selectDependency(page, 'Data JPA') - selectDependency(page, 'Security') - page.generateProject.click() - at HomePage - assertSimpleProject() - .isMavenProject() - .pomAssert() - .hasDependenciesCount(3) - .hasSpringBootStarterDependency('data-jpa') - .hasSpringBootStarterDependency('security') - .hasSpringBootStarterTest() - } - } - - @Test - void selectDependencyTwiceRemovesIt() { - toHome { - selectDependency(page, 'Data JPA') - selectDependency(page, 'Security') - selectDependency(page, 'Security') // remove - page.generateProject.click() - at HomePage - assertSimpleProject() - .isMavenProject() - .pomAssert() - .hasDependenciesCount(2) - .hasSpringBootStarterDependency('data-jpa') - .hasSpringBootStarterTest() - } - } - - @Test - void selectDependencyAndChangeToIncompatibleVersionRemovesIt() { - toHome { - selectDependency(page, 'Data JPA') - selectDependency(page, 'org.acme:bur') - page.bootVersion = '1.0.2.RELEASE' // Bur isn't available anymore - - page.generateProject.click() - at HomePage - assertSimpleProject() - .isMavenProject() - .pomAssert() - .hasSpringBootParent('1.0.2.RELEASE') - .hasDependenciesCount(2) - .hasSpringBootStarterDependency('data-jpa') - .hasSpringBootStarterTest() - } - } - - ProjectAssert assertSimpleProject() { - zipProjectAssert(from('demo.zip')) - .hasBaseDir("demo") - .isJavaProject() - .hasStaticAndTemplatesResources(false) - } - - @Test - void customArtifactIdUpdateNameAutomatically() { - toHome { - page.groupId = 'org.foo' - page.generateProject.click() - at HomePage - zipProjectAssert(from('demo.zip')) - .hasBaseDir("demo") - .isJavaProject('org.foo', 'DemoApplication') - } - } - - @Test - void customGroupIdIdUpdatePackageAutomatically() { - toHome { - page.artifactId = 'my-project' - page.generateProject.click() - at HomePage - zipProjectAssert(from('my-project.zip')) - .hasBaseDir("my-project") - .isJavaProject('com.example', 'MyProjectApplication') - } - } - - @Test - void createGroovyProject() { - toHome { - page.advanced.click() - page.language = 'groovy' - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('demo.zip')) - projectAssert.hasBaseDir('demo') - .isMavenProject() - .isGroovyProject() - .hasStaticAndTemplatesResources(false) - .pomAssert().hasDependenciesCount(3) - .hasSpringBootStarterRootDependency().hasSpringBootStarterTest() - .hasDependency('org.codehaus.groovy', 'groovy') - } - } - - @Test - void createKotlinProject() { - toHome { - page.advanced.click() - page.language = 'kotlin' - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('demo.zip')) - projectAssert.hasBaseDir('demo').isMavenProject().isKotlinProject() - .hasStaticAndTemplatesResources(false) - .pomAssert().hasDependenciesCount(4) - .hasSpringBootStarterRootDependency().hasSpringBootStarterTest() - .hasDependency('org.jetbrains.kotlin', 'kotlin-stdlib') - .hasDependency('org.jetbrains.kotlin', 'kotlin-reflect') - } - } - - @Test - void createWarProject() { - toHome { - page.advanced.click() - page.packaging = 'war' - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('demo.zip')) - projectAssert.hasBaseDir("demo") - .isMavenProject() - .isJavaWarProject() - .pomAssert().hasPackaging('war').hasDependenciesCount(3) - .hasSpringBootStarterDependency('web') // Added with war packaging - .hasSpringBootStarterTomcat() - .hasSpringBootStarterTest() - } - } - - @Test - void createJavaProjectWithCustomDefaults() { - toHome { - page.groupId = 'com.acme' - page.artifactId = 'foo-bar' - page.advanced.click() - page.name = 'My project' - page.description = 'A description for my project' - page.packageName = 'com.example.foo' - page.dependency('web').click() - page.dependency('data-jpa').click() - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('foo-bar.zip')) - projectAssert.hasBaseDir("foo-bar") - .isMavenProject() - .isJavaProject('com.example.foo', 'MyProjectApplication') - .hasStaticAndTemplatesResources(true) - .pomAssert() - .hasGroupId('com.acme') - .hasArtifactId('foo-bar') - .hasName('My project') - .hasDescription('A description for my project') - .hasSpringBootStarterDependency('web') - .hasSpringBootStarterDependency('data-jpa') - .hasSpringBootStarterTest() - } - } - - @Test - void createGroovyProjectWithCustomDefaults() { - toHome { - page.groupId = 'org.biz' - page.artifactId = 'groovy-project' - page.advanced.click() - page.language = 'groovy' - page.name = 'My Groovy project' - page.description = 'A description for my Groovy project' - page.packageName = 'com.example.biz' - page.dependency('web').click() - page.dependency('data-jpa').click() - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('groovy-project.zip')) - projectAssert.hasBaseDir("groovy-project") - .isMavenProject() - .isGroovyProject('com.example.biz', 'MyGroovyProjectApplication') - .hasStaticAndTemplatesResources(true) - .pomAssert() - .hasGroupId('org.biz') - .hasArtifactId('groovy-project') - .hasName('My Groovy project') - .hasDescription('A description for my Groovy project') - .hasSpringBootStarterDependency('web') - .hasSpringBootStarterDependency('data-jpa') - .hasSpringBootStarterTest() - .hasDependency('org.codehaus.groovy', 'groovy') - } - } - - @Test - void createKotlinProjectWithCustomDefaults() { - toHome { - page.groupId = 'org.biz' - page.artifactId = 'kotlin-project' - page.advanced.click() - page.language = 'kotlin' - page.name = 'My Kotlin project' - page.description = 'A description for my Kotlin project' - page.packageName = 'com.example.biz' - page.dependency('web').click() - page.dependency('data-jpa').click() - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('kotlin-project.zip')) - projectAssert.hasBaseDir("kotlin-project").isMavenProject() - .isKotlinProject('com.example.biz', 'MyKotlinProjectApplication') - .hasStaticAndTemplatesResources(true) - .pomAssert().hasGroupId('org.biz').hasArtifactId('kotlin-project') - .hasName('My Kotlin project').hasDescription('A description for my Kotlin project') - .hasSpringBootStarterDependency('web') - .hasSpringBootStarterDependency('data-jpa') - .hasSpringBootStarterTest() - .hasDependency('org.jetbrains.kotlin', 'kotlin-stdlib') - .hasDependency('org.jetbrains.kotlin', 'kotlin-reflect') - } - } - - @Test - void dependencyHiddenAccordingToRange() { - toHome { // bur: [1.1.4.RELEASE,1.2.0.BUILD-SNAPSHOT) - page.advanced.click() - page.dependency('org.acme:bur').displayed == true - - page.bootVersion = '1.0.2.RELEASE' - page.dependency('org.acme:bur').displayed == false - page.dependency('org.acme:biz').displayed == false - page.bootVersion = '1.1.4.RELEASE' - page.dependency('org.acme:bur').displayed == true - page.dependency('org.acme:biz').displayed == false - page.bootVersion = '1.2.0.BUILD-SNAPSHOT' - page.dependency('org.acme:bur').displayed == false - page.dependency('org.acme:biz').displayed == true - } - } - - @Test - void dependencyUncheckedWhenHidden() { - toHome { - page.advanced.click() - page.dependency('org.acme:bur').value() == 'org.acme:bur' - page.bootVersion = '1.0.2.RELEASE' - page.dependency('org.acme:bur').displayed == false - page.bootVersion = '1.1.4.RELEASE' - page.dependency('org.acme:bur').displayed == true - page.dependency('org.acme:bur').value() == false - } - } - - @Test - void customizationShowsUpInDefaultView() { - toHome('/#!language=groovy&packageName=com.example.acme') { - assertEquals 'groovy', page.language.value() - assertEquals 'com.example.acme', page.packageName.value() - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('demo.zip')) - projectAssert.hasBaseDir('demo') - .isMavenProject() - .isGroovyProject('com.example.acme', ProjectAssert.DEFAULT_APPLICATION_NAME) - .hasStaticAndTemplatesResources(false) - .pomAssert().hasDependenciesCount(3) - .hasSpringBootStarterRootDependency().hasSpringBootStarterTest() - .hasDependency('org.codehaus.groovy', 'groovy') - - } - } - - @Test - void customizationsShowsUpWhenViewIsSwitched() { - toHome('/#!packaging=war&javaVersion=1.7') { - assertEquals 'war', page.packaging.value() - assertEquals '1.7', page.javaVersion.value() - page.advanced().click() - assertEquals 'war', page.packaging.value() - assertEquals '1.7', page.javaVersion.value() - page.simple().click() - assertEquals 'war', page.packaging.value() - assertEquals '1.7', page.javaVersion.value() - } - } - - @Test - void customizationsOnGroupIdAndArtifactId() { - toHome('/#!groupId=com.example.acme&artifactId=my-project') { - page.generateProject.click() - at HomePage - def projectAssert = zipProjectAssert(from('my-project.zip')) - projectAssert.hasBaseDir('my-project') - .isMavenProject() - .isJavaProject('com.example.acme', 'MyProjectApplication') - .hasStaticAndTemplatesResources(false) - .pomAssert() - .hasGroupId('com.example.acme') - .hasArtifactId('my-project') - .hasDependenciesCount(2) - .hasSpringBootStarterRootDependency() - .hasSpringBootStarterTest() - } - } - - private Browser toHome(Closure script) { - toHome('/', script) - } - - private Browser toHome(String uri, Closure script) { - browser.go("http://localhost:$port$uri") - browser.at HomePage - script.delegate = browser - script() - browser - } - - private selectDependency(def page, String text) { - page.autocomplete = text - enterAction.perform() - } - - private byte[] from(String fileName) { - getArchive(fileName).bytes - } - - private File getArchive(String fileName) { - File archive = new File(downloadDir, fileName) - assertTrue "Expected content with name $fileName", archive.exists() - archive - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/test/HomePage.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/project/test/HomePage.groovy deleted file mode 100644 index eb0286f8..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/test/HomePage.groovy +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2017 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.test - -import geb.Page - -/** - * A {@link Page} representing the home page of the application. - * - * @author Stephane Nicoll - */ -class HomePage extends Page { - - static at = { title == 'Spring Initializr' } - static content = { - advanced { $('.tofullversion a') } - simple { $('.tosimpleversion a') } - - // Simple view - groupId { $('form').groupId() } - artifactId { $('form').artifactId() } - autocomplete { $('form').autocomplete() } - - // Advanced view - name { $('form').name() } - description { $('form').description() } - packageName { $('form').packageName() } - type { $('form').type() } - packaging { $('form').packaging() } - javaVersion { $('form').javaVersion() } - language { $('form').language() } - - dependency { id -> - $("form").find('input', type: "checkbox", name: "style", value: id) - } - generateProject { $('form').find('button', name: 'generate-project') } - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/support/DefaultDependencyMetadataProviderTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/support/DefaultDependencyMetadataProviderTests.groovy deleted file mode 100644 index bac8aa7f..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/support/DefaultDependencyMetadataProviderTests.groovy +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2012-2016 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.support - -import io.spring.initializr.metadata.BillOfMaterials -import io.spring.initializr.metadata.Dependency -import io.spring.initializr.metadata.DependencyMetadata -import io.spring.initializr.metadata.DependencyMetadataProvider -import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder -import io.spring.initializr.util.Version -import org.junit.Test - -import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertSame; - -/** - * @author Stephane Nicoll - */ -class DefaultDependencyMetadataProviderTests { - - private final DependencyMetadataProvider provider = new DefaultDependencyMetadataProvider() - - @Test - void filterDependencies() { - def first = new Dependency(id: 'first', groupId: 'org.foo', artifactId: 'first', - versionRange: '1.1.4.RELEASE') - def second = new Dependency(id: 'second', groupId: 'org.foo', artifactId: 'second') - def third = new Dependency(id: 'third', groupId: 'org.foo', artifactId: 'third', - versionRange: '1.1.8.RELEASE') - def metadata = InitializrMetadataTestBuilder.withDefaults() - .addDependencyGroup('test', first, second, third).build() - def dependencyMetadata = provider.get(metadata, Version.parse('1.1.5.RELEASE')) - assertEquals 2, dependencyMetadata.dependencies.size() - assertEquals 0, dependencyMetadata.repositories.size() - assertEquals 0, dependencyMetadata.boms.size() - assertSame first, dependencyMetadata.dependencies['first'] - assertSame second, dependencyMetadata.dependencies['second'] - } - - @Test - void resolveDependencies() { - def first = new Dependency(id: 'first', groupId: 'org.foo', artifactId: 'first') - first.mappings << new Dependency.Mapping(versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)', - version: '0.1.0.RELEASE', groupId: 'org.bar', artifactId: 'second') - first.mappings << new Dependency.Mapping(versionRange: '1.1.0.RELEASE', - version: '0.2.0.RELEASE', groupId: 'org.biz', artifactId: 'third') - def second = new Dependency(id: 'second', groupId: 'org.foo', artifactId: 'second') - def metadata = InitializrMetadataTestBuilder.withDefaults() - .addDependencyGroup('test', first, second).build() - - def dependencyMetadata = provider.get(metadata, Version.parse('1.0.5.RELEASE')) - assertEquals 2, dependencyMetadata.dependencies.size() - assertEquals('org.bar', dependencyMetadata.dependencies['first'].groupId) - assertEquals('second', dependencyMetadata.dependencies['first'].artifactId) - assertEquals('0.1.0.RELEASE', dependencyMetadata.dependencies['first'].version) - - def anotherDependencyMetadata = provider.get(metadata, Version.parse('1.1.0.RELEASE')) - assertEquals 2, anotherDependencyMetadata.dependencies.size() - assertEquals('org.biz', anotherDependencyMetadata.dependencies['first'].groupId) - assertEquals('third', anotherDependencyMetadata.dependencies['first'].artifactId) - assertEquals('0.2.0.RELEASE', anotherDependencyMetadata.dependencies['first'].version) - } - - @Test - void addRepoAndRemoveDuplicates() { - def first = new Dependency(id: 'first', groupId: 'org.foo', artifactId: 'first', - repository: 'repo-foo') - def second = new Dependency(id: 'second', groupId: 'org.foo', artifactId: 'second') - def third = new Dependency(id: 'third', groupId: 'org.foo', artifactId: 'third', - repository: 'repo-foo') - def metadata = InitializrMetadataTestBuilder.withDefaults() - .addRepository('repo-foo', 'my-repo', 'http://localhost', false) - .addDependencyGroup('test', first, second, third).build() - def dependencyMetadata = provider.get(metadata, Version.parse('1.1.5.RELEASE')) - assertEquals 3, dependencyMetadata.dependencies.size() - assertEquals 1, dependencyMetadata.repositories.size() - assertEquals 0, dependencyMetadata.boms.size() - assertSame metadata.configuration.env.repositories.get('repo-foo'), - dependencyMetadata.repositories['repo-foo'] - } - - @Test - void addBomAndRemoveDuplicates() { - def first = new Dependency(id: 'first', groupId: 'org.foo', artifactId: 'first', - bom: 'bom-foo') - def second = new Dependency(id: 'second', groupId: 'org.foo', artifactId: 'second') - def third = new Dependency(id: 'third', groupId: 'org.foo', artifactId: 'third', - bom: 'bom-foo') - - def bom = new BillOfMaterials(groupId: 'org.foo', artifactId: 'bom') - bom.mappings << new BillOfMaterials.Mapping(versionRange: '[1.0.0.RELEASE, 1.1.8.RELEASE)', - version: '1.0.0.RELEASE') - bom.mappings << new BillOfMaterials.Mapping(versionRange: '1.1.8.RELEASE', - version: '2.0.0.RELEASE') - def metadata = InitializrMetadataTestBuilder.withDefaults() - .addBom('bom-foo',bom) - .addDependencyGroup('test', first, second, third).build() - def dependencyMetadata = provider.get(metadata, Version.parse('1.1.5.RELEASE')) - assertEquals 3, dependencyMetadata.dependencies.size() - assertEquals 0, dependencyMetadata.repositories.size() - assertEquals 1, dependencyMetadata.boms.size() - assertEquals 'org.foo', dependencyMetadata.boms['bom-foo'].groupId - assertEquals 'bom', dependencyMetadata.boms['bom-foo'].artifactId - assertEquals '1.0.0.RELEASE', dependencyMetadata.boms['bom-foo'].version - } - - @Test - void repoFromBomAccordingToVersion() { - def dependencyMetadata = testRepoFromBomAccordingToVersion('1.0.9.RELEASE') - assertEquals(Version.parse('1.0.9.RELEASE'), dependencyMetadata.bootVersion) - assertEquals 3, dependencyMetadata.dependencies.size() - assertEquals 2, dependencyMetadata.repositories.size() - assertEquals 1, dependencyMetadata.boms.size() - assertEquals 'foo', dependencyMetadata.repositories['repo-foo'].name - assertEquals 'bar', dependencyMetadata.repositories['repo-bar'].name - assertEquals 'org.foo', dependencyMetadata.boms['bom-foo'].groupId - assertEquals 'bom', dependencyMetadata.boms['bom-foo'].artifactId - assertEquals '2.0.0.RELEASE', dependencyMetadata.boms['bom-foo'].version - } - - @Test - void repoFromBomAccordingToAnotherVersion() { - def dependencyMetadata = testRepoFromBomAccordingToVersion('1.1.5.RELEASE') - assertEquals(Version.parse('1.1.5.RELEASE'), dependencyMetadata.bootVersion) - assertEquals 3, dependencyMetadata.dependencies.size() - assertEquals 2, dependencyMetadata.repositories.size() - assertEquals 1, dependencyMetadata.boms.size() - assertEquals 'foo', dependencyMetadata.repositories['repo-foo'].name - assertEquals 'biz', dependencyMetadata.repositories['repo-biz'].name - assertEquals 'org.foo', dependencyMetadata.boms['bom-foo'].groupId - assertEquals 'bom', dependencyMetadata.boms['bom-foo'].artifactId - assertEquals '3.0.0.RELEASE', dependencyMetadata.boms['bom-foo'].version - } - - private DependencyMetadata testRepoFromBomAccordingToVersion(bootVersion) { - def first = new Dependency(id: 'first', groupId: 'org.foo', artifactId: 'first', - repository: 'repo-foo') - def second = new Dependency(id: 'second', groupId: 'org.foo', artifactId: 'second') - def third = new Dependency(id: 'third', groupId: 'org.foo', artifactId: 'third', - bom: 'bom-foo') - - BillOfMaterials bom = new BillOfMaterials(groupId: 'org.foo', artifactId: 'bom') - bom.mappings << new BillOfMaterials.Mapping(versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)', - version: '2.0.0.RELEASE', repositories: ['repo-foo', 'repo-bar']) - bom.mappings << new BillOfMaterials.Mapping(versionRange: '1.1.0.RELEASE', - version: '3.0.0.RELEASE', repositories: ['repo-biz']) - def metadata = InitializrMetadataTestBuilder.withDefaults() - .addBom('bom-foo', bom) - .addRepository('repo-foo', 'foo', 'http://localhost', false) - .addRepository('repo-bar', 'bar', 'http://localhost', false) - .addRepository('repo-biz', 'biz', 'http://localhost', false) - .addDependencyGroup('test', first, second, third).build() - provider.get(metadata, Version.parse(bootVersion)) - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/support/DefaultInitializrMetadataProviderTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/support/DefaultInitializrMetadataProviderTests.groovy deleted file mode 100644 index 4a07c59d..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/support/DefaultInitializrMetadataProviderTests.groovy +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2012-2016 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.support - -import io.spring.initializr.metadata.DefaultMetadataElement -import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder -import org.junit.Before -import org.junit.Test - -import org.springframework.core.io.ClassPathResource -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.client.MockRestServiceServer -import org.springframework.web.client.RestTemplate - -import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertNotNull -import static org.springframework.test.web.client.match.MockRestRequestMatchers.method -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus - -/** - * @author Stephane Nicoll - */ -class DefaultInitializrMetadataProviderTests { - - private RestTemplate restTemplate - - private MockRestServiceServer mockServer - - @Before - public void setUp() { - restTemplate = new RestTemplate() - mockServer = MockRestServiceServer.createServer(restTemplate); - } - - @Test - void bootVersionsAreReplaced() { - def metadata = new InitializrMetadataTestBuilder() - .addBootVersion('0.0.9.RELEASE', true).addBootVersion('0.0.8.RELEASE', false).build() - assertEquals '0.0.9.RELEASE', metadata.bootVersions.default.id - def provider = new DefaultInitializrMetadataProvider(metadata, restTemplate) - expectJson(metadata.configuration.env.springBootMetadataUrl, - "metadata/sagan/spring-boot.json"); - - def updatedMetadata = provider.get() - assertNotNull updatedMetadata.bootVersions - def updatedBootVersions = updatedMetadata.bootVersions.content - assertEquals 4, updatedBootVersions.size() - assertBootVersion(updatedBootVersions[0], '1.4.1 (SNAPSHOT)', false) - assertBootVersion(updatedBootVersions[1], '1.4.0', true) - assertBootVersion(updatedBootVersions[2], '1.3.8 (SNAPSHOT)', false) - assertBootVersion(updatedBootVersions[3], '1.3.7', false) - } - - @Test - void defaultBootVersionIsAlwaysSet() { - def metadata = new InitializrMetadataTestBuilder() - .addBootVersion('0.0.9.RELEASE', true).addBootVersion('0.0.8.RELEASE', false).build() - assertEquals '0.0.9.RELEASE', metadata.bootVersions.default.id - def provider = new DefaultInitializrMetadataProvider(metadata, restTemplate) - expectJson(metadata.configuration.env.springBootMetadataUrl, - "metadata/sagan/spring-boot-no-default.json"); - - def updatedMetadata = provider.get() - assertNotNull updatedMetadata.bootVersions - def updatedBootVersions = updatedMetadata.bootVersions.content - assertEquals 4, updatedBootVersions.size() - assertBootVersion(updatedBootVersions[0], '1.3.1 (SNAPSHOT)', true) - assertBootVersion(updatedBootVersions[1], '1.3.0', false) - assertBootVersion(updatedBootVersions[2], '1.2.6 (SNAPSHOT)', false) - assertBootVersion(updatedBootVersions[3], '1.2.5', false) - } - - private static void assertBootVersion(DefaultMetadataElement actual, String name, boolean defaultVersion) { - assertEquals name, actual.name - assertEquals defaultVersion, actual.default - } - - private void expectJson(String url, String bodyPath) { - HttpHeaders httpHeaders = new HttpHeaders() - httpHeaders.setContentType(MediaType.APPLICATION_JSON) - this.mockServer.expect(requestTo(url)) - .andExpect(method(HttpMethod.GET)) - .andRespond(withStatus(HttpStatus.OK) - .body(new ClassPathResource(bodyPath)) - .headers(httpHeaders)) - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/support/SpringBootMetadataReaderTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/support/SpringBootMetadataReaderTests.groovy deleted file mode 100644 index 8772cd52..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/support/SpringBootMetadataReaderTests.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-2016 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.support - -import io.spring.initializr.metadata.InitializrMetadata -import io.spring.initializr.metadata.InitializrMetadataBuilder -import org.junit.Test -import org.springframework.core.io.ClassPathResource -import org.springframework.http.MediaType -import org.springframework.test.web.client.MockRestServiceServer -import org.springframework.web.client.RestTemplate - -import static org.junit.Assert.assertNotNull -import static org.junit.Assert.fail -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess - -/** - * @author Stephane Nicoll - * @author Dave Syer - */ -class SpringBootMetadataReaderTests { - - private final InitializrMetadata metadata = InitializrMetadataBuilder.create().build() - - private final RestTemplate restTemplate = new RestTemplate() - - private final MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build() - - @Test - void readAvailableVersions() { - server.expect(requestTo("https://spring.io/project_metadata/spring-boot")).andRespond( - withSuccess(new ClassPathResource('metadata/sagan/spring-boot.json'), MediaType.APPLICATION_JSON)) - def versions = new SpringBootMetadataReader(restTemplate, - metadata.configuration.env.springBootMetadataUrl).bootVersions - assertNotNull "spring boot versions should not be null", versions - boolean defaultFound - versions.each { - assertNotNull 'Id must be set', it.id - assertNotNull 'Name must be set', it.name - if (it.default) { - if (defaultFound) { - fail("One default version was already found $it.id") - } - defaultFound = true - } - } - server.verify() - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/ui/UiControllerIntegrationTests.groovy b/initializr-web/src/test/groovy/io/spring/initializr/web/ui/UiControllerIntegrationTests.groovy deleted file mode 100644 index 89d82497..00000000 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/ui/UiControllerIntegrationTests.groovy +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012-2016 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.ui - -import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests -import org.json.JSONObject -import org.junit.Test -import org.skyscreamer.jsonassert.JSONAssert -import org.skyscreamer.jsonassert.JSONCompareMode - -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.test.context.ActiveProfiles - -/** - * @author Stephane Nicoll - */ -@ActiveProfiles('test-default') -class UiControllerIntegrationTests - extends AbstractInitializrControllerIntegrationTests { - - @Test - void dependenciesNoVersion() { - ResponseEntity response = execute('/ui/dependencies', String, null, null) - validateContentType(response, MediaType.APPLICATION_JSON) - validateDependenciesOutput('all', new JSONObject(response.body)) - } - - @Test - void dependenciesSpecificVersion() { - ResponseEntity response = execute('/ui/dependencies?version=1.1.2.RELEASE', String, null, null) - validateContentType(response, MediaType.APPLICATION_JSON) - validateDependenciesOutput('1.1.2', new JSONObject(response.body)) - } - - protected void validateDependenciesOutput(String version, JSONObject actual) { - def expected = readJsonFrom("metadata/ui/test-dependencies-$version" + ".json") - JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT) - } - -} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/AbstractFullStackInitializrIntegrationTests.groovy b/initializr-web/src/test/java/io/spring/initializr/web/AbstractFullStackInitializrIntegrationTests.java similarity index 63% rename from initializr-web/src/test/groovy/io/spring/initializr/web/AbstractFullStackInitializrIntegrationTests.groovy rename to initializr-web/src/test/java/io/spring/initializr/web/AbstractFullStackInitializrIntegrationTests.java index cc115fb7..d3572d75 100644 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/AbstractFullStackInitializrIntegrationTests.groovy +++ b/initializr-web/src/test/java/io/spring/initializr/web/AbstractFullStackInitializrIntegrationTests.java @@ -14,32 +14,33 @@ * limitations under the License. */ -package io.spring.initializr.web +package io.spring.initializr.web; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT -import io.spring.initializr.web.AbstractInitializrIntegrationTests.Config +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +import io.spring.initializr.web.AbstractInitializrIntegrationTests.Config; -import org.junit.runner.RunWith -import org.springframework.boot.context.embedded.LocalServerPort -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.junit4.SpringRunner +import org.junit.runner.RunWith; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; /** * @author Stephane Nicoll * @author Dave Syer */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = Config, webEnvironment = RANDOM_PORT) -abstract class AbstractFullStackInitializrIntegrationTests +@SpringBootTest(classes = Config.class, webEnvironment = RANDOM_PORT) +public abstract class AbstractFullStackInitializrIntegrationTests extends AbstractInitializrIntegrationTests { @LocalServerPort - protected int port + protected int port; - String host = "localhost" + protected String host = "localhost"; - String createUrl(String context) { - "http://${host}:${port}" + (context.startsWith('/') ? context : '/' + context) + protected String createUrl(String context) { + return "http://" + host + ":" + port + + (context.startsWith("/") ? context : "/" + context); } } diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/AbstractInitializrControllerIntegrationTests.groovy b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrControllerIntegrationTests.java similarity index 53% rename from initializr-web/src/test/groovy/io/spring/initializr/web/AbstractInitializrControllerIntegrationTests.groovy rename to initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrControllerIntegrationTests.java index c6c393e4..d9cf4e15 100644 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/AbstractInitializrControllerIntegrationTests.groovy +++ b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrControllerIntegrationTests.java @@ -14,41 +14,45 @@ * limitations under the License. */ -package io.spring.initializr.web +package io.spring.initializr.web; -import io.spring.initializr.web.test.MockMvcClientHttpRequestFactory -import io.spring.initializr.web.test.MockMvcClientHttpRequestFactoryTestExecutionListener +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.TestExecutionListeners.MergeMode; -import org.springframework.beans.factory.BeanFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.web.client.RestTemplateCustomizer -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.TestExecutionListeners -import org.springframework.test.context.TestExecutionListeners.MergeMode +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests.RestTemplateConfig; +import io.spring.initializr.web.test.MockMvcClientHttpRequestFactory; +import io.spring.initializr.web.test.MockMvcClientHttpRequestFactoryTestExecutionListener; /** * @author Stephane Nicoll */ -@ContextConfiguration(classes = RestTemplateConfig) -@TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS, - listeners = MockMvcClientHttpRequestFactoryTestExecutionListener) +@ContextConfiguration(classes = RestTemplateConfig.class) +@TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS, listeners = MockMvcClientHttpRequestFactoryTestExecutionListener.class) @AutoConfigureMockMvc -@AutoConfigureRestDocs(outputDir = "target/snippets", uriPort = 80, - uriHost = "start.spring.io") -abstract class AbstractInitializrControllerIntegrationTests +@AutoConfigureRestDocs(outputDir = "target/snippets", uriPort = 80, uriHost = "start.spring.io") +public abstract class AbstractInitializrControllerIntegrationTests extends AbstractInitializrIntegrationTests { - String host = "start.spring.io" - - @Autowired - MockMvcClientHttpRequestFactory requests + protected String host = "start.spring.io"; - String createUrl(String context) { - context.startsWith('/') ? context : '/' + context + @Autowired + private + MockMvcClientHttpRequestFactory requests; + + protected String createUrl(String context) { + return context.startsWith("/") ? context : "/" + context; + } + + public MockMvcClientHttpRequestFactory getRequests() { + return requests; } @Configuration @@ -56,9 +60,10 @@ abstract class AbstractInitializrControllerIntegrationTests @Bean RestTemplateCustomizer mockMvcCustomizer(BeanFactory beanFactory) { - { template -> - template.setRequestFactory(beanFactory.getBean(MockMvcClientHttpRequestFactory)) - } + return template -> { + template.setRequestFactory( + beanFactory.getBean(MockMvcClientHttpRequestFactory.class)); + }; } } } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java new file mode 100644 index 00000000..2a0d172d --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java @@ -0,0 +1,266 @@ +/* + * Copyright 2012-2016 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 static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.rauschig.jarchivelib.ArchiverFactory; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.RestTemplate; + +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; + +/** + * @author Stephane Nicoll + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Config.class) +public abstract class AbstractInitializrIntegrationTests { + + protected static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1 + .getMediaType(); + + @Rule + public final TemporaryFolder folder = new TemporaryFolder(); + + @Autowired + private RestTemplateBuilder restTemplateBuilder; + + private RestTemplate restTemplate; + + @Before + public void before() { + restTemplate = restTemplateBuilder.build(); + } + + protected abstract String createUrl(String context); + + protected String htmlHome() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + return restTemplate.exchange(createUrl("/"), HttpMethod.GET, + new HttpEntity(headers), String.class).getBody(); + } + + /** + * Validate the "Content-Type" header of the specified response. + */ + protected void validateContentType(ResponseEntity response, + MediaType expected) { + MediaType actual = response.getHeaders().getContentType(); + assertTrue("Non compatible media-type, expected " + expected + ", got " + actual, + actual.isCompatibleWith(expected)); + } + + protected void validateMetadata(ResponseEntity response, MediaType mediaType, + String version, JSONCompareMode compareMode) { + validateContentType(response, mediaType); + JSONObject json = new JSONObject(response.getBody()); + JSONObject expected = readMetadataJson(version); + JSONAssert.assertEquals(expected, json, compareMode); + } + + protected void validateCurrentMetadata(ResponseEntity response) { + validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(new JSONObject(response.getBody())); + } + + protected void validateCurrentMetadata(JSONObject json) { + JSONObject expected = readMetadataJson("2.1.0"); + JSONAssert.assertEquals(expected, json, JSONCompareMode.STRICT); + } + + private JSONObject readMetadataJson(String version) { + return readJsonFrom("metadata/test-default-" + version + ".json"); + } + + /** + * Return a {@link ProjectAssert} for the following archive content. + */ + protected ProjectAssert zipProjectAssert(byte[] content) { + return projectAssert(content, ArchiveType.ZIP); + } + + /** + * Return a {@link ProjectAssert} for the following TGZ archive. + */ + protected ProjectAssert tgzProjectAssert(byte[] content) { + return projectAssert(content, ArchiveType.TGZ); + } + + protected ProjectAssert downloadZip(String context) { + byte[] body = downloadArchive(context); + return zipProjectAssert(body); + } + + protected ProjectAssert downloadTgz(String context) { + byte[] body = downloadArchive(context); + return tgzProjectAssert(body); + } + + protected byte[] downloadArchive(String context) { + return restTemplate.getForObject(createUrl(context), byte[].class); + } + + protected ResponseEntity invokeHome(String userAgentHeader, + String... acceptHeaders) { + return execute("/", String.class, userAgentHeader, acceptHeaders); + } + + protected ResponseEntity execute(String contextPath, Class responseType, + String userAgentHeader, String... acceptHeaders) { + HttpHeaders headers = new HttpHeaders(); + if (userAgentHeader != null) { + headers.set("User-Agent", userAgentHeader); + } + if (acceptHeaders != null) { + List mediaTypes = new ArrayList<>(); + for (String acceptHeader : acceptHeaders) { + mediaTypes.add(MediaType.parseMediaType(acceptHeader)); + } + headers.setAccept(mediaTypes); + } + else { + headers.setAccept(Collections.emptyList()); + } + return restTemplate.exchange(createUrl(contextPath), HttpMethod.GET, + new HttpEntity(headers), responseType); + } + + protected ProjectAssert projectAssert(byte[] content, ArchiveType archiveType) { + try { + File archiveFile = writeArchive(content); + + File project = folder.newFolder(); + switch (archiveType) { + case ZIP: + ArchiverFactory.createArchiver("zip").extract(archiveFile, project); + break; + case TGZ: + ArchiverFactory.createArchiver("tar", "gz").extract(archiveFile, project); + break; + } + return new ProjectAssert(project); + } + catch (Exception e) { + throw new IllegalStateException("Cannot unpack archive", e); + } + } + + protected File writeArchive(byte[] body) throws IOException { + File archiveFile = folder.newFile(); + FileOutputStream stream = new FileOutputStream(archiveFile); + try { + stream.write(body); + } + finally { + stream.close(); + } + return archiveFile; + } + + protected JSONObject readJsonFrom(String path) { + try { + ClassPathResource resource = new ClassPathResource(path); + InputStream stream = resource.getInputStream(); + try { + String json = StreamUtils.copyToString(stream, Charset.forName("UTF-8")); + String placeholder = ""; + if (this instanceof AbstractInitializrControllerIntegrationTests) { + placeholder = ((AbstractInitializrControllerIntegrationTests) this).host; + } + if (this instanceof AbstractFullStackInitializrIntegrationTests) { + AbstractFullStackInitializrIntegrationTests test = (AbstractFullStackInitializrIntegrationTests) this; + placeholder = test.host + ":" + test.port; + } + // Let's parse the port as it is random + // TODO: put the port back somehow so it appears in stubs + String content = json.replaceAll("@host@", placeholder); + return new JSONObject(content); + } + finally { + stream.close(); + } + } + catch (Exception e) { + throw new IllegalStateException("Cannot read JSON from path=" + path); + } + } + + public RestTemplate getRestTemplate() { + return restTemplate; + } + + private enum ArchiveType { + ZIP, + + TGZ; + } + + @EnableAutoConfiguration + public static class Config { + + @Bean + public InitializrMetadataProvider initializrMetadataProvider( + InitializrProperties properties) { + return new DefaultInitializrMetadataProvider(InitializrMetadataBuilder + .fromInitializrProperties(properties).build(), new RestTemplate()) { + @Override + protected void updateInitializrMetadata(InitializrMetadata metadata) { + // Disable metadata fetching from spring.io + } + }; + } + + } +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessorTests.java b/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessorTests.java new file mode 100644 index 00000000..d4e059db --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/CloudfoundryEnvironmentPostProcessorTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 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.autoconfigure; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.mock.env.MockEnvironment; + +/** + * @author Stephane Nicoll + */ +public class CloudfoundryEnvironmentPostProcessorTests { + + private final CloudfoundryEnvironmentPostProcessor postProcessor = new CloudfoundryEnvironmentPostProcessor(); + private final MockEnvironment environment = new MockEnvironment(); + private final SpringApplication application = new SpringApplication(); + + @Test + public void parseCredentials() { + environment.setProperty("vcap.services.stats-index.credentials.uri", + "http://user:pass@example.com/bar/biz?param=one"); + postProcessor.postProcessEnvironment(environment, application); + + assertThat(environment.getProperty("initializr.stats.elastic.uri"), + is("http://example.com/bar/biz?param=one")); + assertThat(environment.getProperty("initializr.stats.elastic.username"), is("user")); + assertThat(environment.getProperty("initializr.stats.elastic.password"), is("pass")); + } + + @Test + public void parseNoCredentials() { + environment.setProperty("vcap.services.stats-index.credentials.uri", + "http://example.com/bar/biz?param=one"); + postProcessor.postProcessEnvironment(environment, application); + + assertThat(environment.getProperty("initializr.stats.elastic.uri"), + is("http://example.com/bar/biz?param=one")); + assertThat(environment.getProperty("initializr.stats.elastic.username"), is(nullValue())); + assertThat(environment.getProperty("initializr.stats.elastic.password"), is(nullValue())); + } + + @Test + public void parseNoVcapUri() { + postProcessor.postProcessEnvironment(environment, application); + + assertThat(environment.getProperty("initializr.stats.elastic.uri"), is(nullValue())); + assertThat(environment.getProperty("initializr.stats.elastic.username"), is(nullValue())); + assertThat(environment.getProperty("initializr.stats.elastic.password"), is(nullValue())); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/mapper/DependencyMetadataJsonMapperTests.java b/initializr-web/src/test/java/io/spring/initializr/web/mapper/DependencyMetadataJsonMapperTests.java new file mode 100644 index 00000000..7406919e --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/mapper/DependencyMetadataJsonMapperTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2015 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.mapper; + +import static org.junit.Assert.assertEquals; + +import java.net.URL; +import java.util.Collections; + +import org.json.JSONObject; +import org.junit.Test; + +import io.spring.initializr.metadata.BillOfMaterials; +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.DependencyMetadata; +import io.spring.initializr.metadata.Repository; +import io.spring.initializr.util.Version; + +/** + * @author Stephane Nicoll + */ +public class DependencyMetadataJsonMapperTests { + + private final DependencyMetadataJsonMapper mapper = new DependencyMetadataV21JsonMapper(); + + @Test + public void mapDependency() throws Exception { + Dependency d = Dependency.withId("foo", "org.foo", "foo"); + d.setRepository("my-repo"); + d.setBom("my-bom"); + Repository repository = new Repository(); + repository.setName("foo-repo"); + repository.setUrl(new URL("http://example.com/foo")); + BillOfMaterials bom = BillOfMaterials.create("org.foo", "foo-bom", + "1.0.0.RELEASE"); + DependencyMetadata metadata = new DependencyMetadata( + Version.parse("1.2.0.RELEASE"), Collections.singletonMap(d.getId(), d), + Collections.singletonMap("repo-id", repository), + Collections.singletonMap("bom-id", bom)); + JSONObject content = new JSONObject(mapper.write(metadata)); + assertEquals("my-bom", content.getJSONObject("dependencies").getJSONObject("foo") + .getString("bom")); + assertEquals("my-repo", content.getJSONObject("dependencies").getJSONObject("foo") + .getString("repository")); + assertEquals("foo-repo", content.getJSONObject("repositories") + .getJSONObject("repo-id").getString("name")); + assertEquals("foo-bom", content.getJSONObject("boms").getJSONObject("bom-id") + .getString("artifactId")); + assertEquals("1.0.0.RELEASE", content.getJSONObject("boms") + .getJSONObject("bom-id").getString("version")); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.java b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.java new file mode 100644 index 00000000..73e269db --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2017 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.mapper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.json.JSONObject; +import org.junit.Test; + +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.Link; +import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder; + +/** + * @author Stephane Nicoll + */ +public class InitializrMetadataJsonMapperTests { + + private final InitializrMetadataJsonMapper jsonMapper = new InitializrMetadataV21JsonMapper(); + + @Test + public void withNoAppUrl() { + InitializrMetadata metadata = new InitializrMetadataTestBuilder().addType("foo", true, "/foo.zip", "none", "test") + .addDependencyGroup("foo", "one", "two").build(); + String json = jsonMapper.write(metadata, null); + JSONObject result = new JSONObject(json); + assertEquals("/foo.zip?type=foo{&dependencies,packaging,javaVersion,language,bootVersion," + + "groupId,artifactId,version,name,description,packageName}", get(result, "_links.foo.href")); + } + + @Test + public void withAppUrl() { + InitializrMetadata metadata = new InitializrMetadataTestBuilder().addType("foo", true, "/foo.zip", "none", "test") + .addDependencyGroup("foo", "one", "two").build(); + String json = jsonMapper.write(metadata, "http://server:8080/my-app"); + JSONObject result = new JSONObject(json); + assertEquals("http://server:8080/my-app/foo.zip?type=foo{&dependencies,packaging,javaVersion," + + "language,bootVersion,groupId,artifactId,version,name,description,packageName}", + get(result, "_links.foo.href")); + } + + @Test + public void linksRendered() { + Dependency dependency = Dependency.withId("foo", "com.example", "foo"); + dependency.getLinks().add(Link.create("guide", "https://example.com/how-to")); + dependency.getLinks().add(Link.create("reference", "https://example.com/doc")); + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() + .addDependencyGroup("test", dependency).build(); + String json = jsonMapper.write(metadata, null); + int first = json.indexOf("https://example.com/how-to"); + int second = json.indexOf("https://example.com/doc"); + // JSON objects are not ordered + assertTrue(first>0); + assertTrue(second>0); + } + + private Object get(JSONObject result, String path) { + String[] nodes = path.split("\\."); + for (int i = 0; i < nodes.length-1; i++) { + String node = nodes[i]; + result = result.getJSONObject(node); + } + return result.getString(nodes[nodes.length-1]); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/mapper/LinkMapperTests.java b/initializr-web/src/test/java/io/spring/initializr/web/mapper/LinkMapperTests.java new file mode 100644 index 00000000..99526a2a --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/mapper/LinkMapperTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2017 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.mapper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import io.spring.initializr.metadata.Link; + +/** + * Tests for {@link LinkMapper}. + * + * @author Stephane Nicoll + */ +public class LinkMapperTests { + + @Test + public void mapSimpleRel() { + List links = new ArrayList(); + links.add(Link.create("a", "https://example.com", "some description")); + Map model = (Map) LinkMapper.mapLinks(links); + assertEquals(1, model.size()); + assertTrue(model.containsKey("a")); + @SuppressWarnings("unchecked") + Map linkModel = (Map) model.get("a"); + assertEquals(2, linkModel.size()); + assertEquals("https://example.com", linkModel.get("href")); + assertEquals("some description", linkModel.get("title")); + } + + @Test + public void mapTemplatedRel() { + List links = new ArrayList(); + links.add(Link.create("a", "https://example.com/{bootVersion}/a", true)); + Map model = (Map) LinkMapper.mapLinks(links); + assertEquals(1, model.size()); + assertTrue(model.containsKey("a")); + @SuppressWarnings("unchecked") + Map linkModel = (Map) model.get("a"); + assertEquals(2, linkModel.size()); + assertEquals("https://example.com/{bootVersion}/a", linkModel.get("href")); + assertEquals(true, linkModel.get("templated")); + } + + @Test + public void mergeSeveralLinksInArray() { + List links = new ArrayList(); + links.add(Link.create("a", "https://example.com", "some description")); + links.add(Link.create("a", "https://example.com/2")); + Map model = (Map) LinkMapper.mapLinks(links); + assertEquals(1, model.size()); + assertTrue(model.containsKey("a")); + @SuppressWarnings("unchecked") + List> linksModel = (List>) model.get("a"); + assertEquals(2, linksModel.size()); + assertEquals("https://example.com", linksModel.get(0).get("href")); + assertEquals("https://example.com/2", linksModel.get(1).get("href")); + } + + @Test + public void keepOrdering() { + List links = new ArrayList(); + links.add(Link.create("a", "https://example.com")); + links.add(Link.create("b", "https://example.com")); + Map model = (Map) LinkMapper.mapLinks(links); + assertEquals("[a, b]", model.keySet().toString()); + } + + @Test + public void keepOrderingWithMultipleUrlForSameRel() { + List links = new ArrayList(); + links.add(Link.create("a", "https://example.com")); + links.add(Link.create("b", "https://example.com")); + links.add(Link.create("a", "https://example.com")); + Map model = (Map) LinkMapper.mapLinks(links); + assertEquals("[a, b]", model.keySet().toString()); + } + +} diff --git a/initializr-web/src/test/groovy/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.groovy b/initializr-web/src/test/java/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.java similarity index 54% rename from initializr-web/src/test/groovy/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.groovy rename to initializr-web/src/test/java/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.java index 8b8aedfe..b1a78cf8 100644 --- a/initializr-web/src/test/groovy/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.groovy +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package io.spring.initializr.web.project +package io.spring.initializr.web.project; -import io.spring.initializr.test.generator.PomAssert -import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests -import org.junit.Test +import org.junit.Test; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ActiveProfiles +import io.spring.initializr.test.generator.PomAssert; +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; /** * Validate that the "raw" HTTP commands that are described in the @@ -31,44 +32,44 @@ import org.springframework.test.context.ActiveProfiles * * @author Stephane Nicoll */ -@ActiveProfiles('test-default') -class CommandLineExampleIntegrationTests extends AbstractInitializrControllerIntegrationTests { +@ActiveProfiles("test-default") +public class CommandLineExampleIntegrationTests extends AbstractInitializrControllerIntegrationTests { @Test - void generateDefaultProject() { - downloadZip('/starter.zip').isJavaProject() + public void generateDefaultProject() { + downloadZip("/starter.zip").isJavaProject() .isMavenProject().hasStaticAndTemplatesResources(false).pomAssert() .hasSpringBootStarterRootDependency() .hasSpringBootStarterTest() - .hasDependenciesCount(2) + .hasDependenciesCount(2); } @Test - void generateWebProjectWithJava8() { - downloadZip('/starter.zip?dependencies=web&javaVersion=1.8').isJavaProject() + public void generateWebProjectWithJava8() { + downloadZip("/starter.zip?dependencies=web&javaVersion=1.8").isJavaProject() .isMavenProject().hasStaticAndTemplatesResources(true).pomAssert() - .hasJavaVersion('1.8') - .hasSpringBootStarterDependency('web') + .hasJavaVersion("1.8") + .hasSpringBootStarterDependency("web") .hasSpringBootStarterTest() - .hasDependenciesCount(2) + .hasDependenciesCount(2); } @Test - void generateWebDataJpaGradleProject() { - downloadTgz('/starter.tgz?dependencies=web,data-jpa&type=gradle-project&baseDir=my-dir') - .hasBaseDir('my-dir') + public void generateWebDataJpaGradleProject() { + downloadTgz("/starter.tgz?dependencies=web,data-jpa&type=gradle-project&baseDir=my-dir") + .hasBaseDir("my-dir") .isJavaProject() .isGradleProject().hasStaticAndTemplatesResources(true) .gradleBuildAssert() - .contains('spring-boot-starter-web') - .contains('spring-boot-starter-data-jpa') + .contains("spring-boot-starter-web") + .contains("spring-boot-starter-data-jpa"); } @Test - void generateMavenPomWithWarPackaging() { - def response = restTemplate.getForEntity(createUrl('/pom.xml?packaging=war'), String) - PomAssert pomAssert = new PomAssert(response.body) - pomAssert.hasPackaging('war') + public void generateMavenPomWithWarPackaging() { + ResponseEntity response = getRestTemplate().getForEntity(createUrl("/pom.xml?packaging=war"), String.class); + PomAssert pomAssert = new PomAssert(response.getBody()); + pomAssert.hasPackaging("war"); } } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/LegacyStsControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/project/LegacyStsControllerIntegrationTests.java new file mode 100644 index 00000000..802903d9 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/LegacyStsControllerIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2016 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 static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.servlet.resource.ResourceUrlProvider; + +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import io.spring.initializr.web.project.LegacyStsControllerIntegrationTests.LegacyConfig; + +/** + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +@ContextConfiguration(classes = LegacyConfig.class) +public class LegacyStsControllerIntegrationTests + extends AbstractInitializrControllerIntegrationTests { + + @Test + public void legacyStsHome() { + String body = htmlHome(); + assertTrue("groupId not found", body.contains("com.example")); + assertTrue("artifactId not found", body.contains("demo")); + assertTrue("custom description not found", body.contains("Demo project for Spring Boot")); + assertTrue ("Wrong body:\n" + body, body.contains("")); + assertTrue ("Wrong body:\n" + body, body.contains("")); + } + + protected String htmlHome() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + return getRestTemplate().exchange(createUrl("/sts"), HttpMethod.GET, + new HttpEntity(headers), String.class).getBody(); + } + + @Configuration + protected static class LegacyConfig { + + @Bean + public LegacyStsController legacyStsController( + InitializrMetadataProvider metadataProvider, + ResourceUrlProvider resourceUrlProvider) { + return new LegacyStsController(metadataProvider, resourceUrlProvider); + } + + } + +} \ No newline at end of file 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/project/MainControllerDefaultsIntegrationTests.java new file mode 100644 index 00000000..807bdfd3 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDefaultsIntegrationTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2016 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 static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.test.context.ActiveProfiles; + +import io.spring.initializr.test.generator.PomAssert; +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; + +/** + * @author Stephane Nicoll + */ +@ActiveProfiles({"test-default", "test-custom-defaults"}) +public class MainControllerDefaultsIntegrationTests extends AbstractInitializrControllerIntegrationTests { + + // see defaults customization + + @Test + public void generateDefaultPom() { + String content = getRestTemplate().getForObject(createUrl("/pom.xml?style=web"), 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"); + } + + @Test + public void defaultsAppliedToHome() { + String body = htmlHome(); + assertTrue("custom groupId not found", body.contains("org.foo")); + assertTrue("custom artifactId not found", body.contains("foo-bar")); + assertTrue("custom name not found", body.contains("FooBar")); + assertTrue("custom description not found", body.contains("FooBar Project")); + assertTrue("custom package not found", body.contains("org.foo.demo")); + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..bd52a923 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDependenciesTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2016 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.web.AbstractInitializrControllerIntegrationTests; +import org.json.JSONObject; +import org.junit.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.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; + +/** + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +public class MainControllerDependenciesTests extends AbstractInitializrControllerIntegrationTests { + + @Test + public void noBootVersion() { + ResponseEntity response = execute("/dependencies", String.class, null, "application/json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + validateContentType(response, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateDependenciesOutput("1.1.4", new JSONObject(response.getBody())); + } + + @Test + public void filteredDependencies() { + ResponseEntity response = execute("/dependencies?bootVersion=1.2.1.RELEASE", + String.class, null, "application/json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + validateContentType(response, AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateDependenciesOutput("1.2.1", new JSONObject(response.getBody())); + } + + protected void validateDependenciesOutput(String version, JSONObject actual) { + JSONObject expected = readJsonFrom("metadata/dependencies/test-dependencies-" + version + ".json"); + JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT); + } + +} 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/project/MainControllerEnvIntegrationTests.java new file mode 100644 index 00000000..130160a6 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerEnvIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2016 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.URI; + +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +import io.spring.initializr.test.generator.ProjectAssert; +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; + +/** + * @author Stephane Nicoll + */ +@ActiveProfiles({"test-default", "test-custom-env"}) +public class MainControllerEnvIntegrationTests extends AbstractInitializrControllerIntegrationTests { + + @Test + public void downloadCliWithCustomRepository() throws Exception { + ResponseEntity entity = getRestTemplate().getForEntity(createUrl("/spring"), String.class); + assertEquals(HttpStatus.FOUND, entity.getStatusCode()); + String expected = "https://repo.spring.io/lib-release/org/springframework/boot/spring-boot-cli/1.1.4.RELEASE/spring-boot-cli-1.1.4.RELEASE-bin.zip"; + assertEquals(new URI(expected), entity.getHeaders().getLocation()); + } + + @Test + public void doNotForceSsl() { + ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); + String body = response.getBody(); + assertTrue("Must not force https", body.contains("http://start.spring.io/")); + assertFalse("Must not force https", body.contains("https://")); + } + + @Test + public void generateProjectWithInvalidName() { + downloadZip("/starter.zip?style=data-jpa&name=Invalid") + .isJavaProject((String) ProjectAssert.DEFAULT_PACKAGE_NAME, "FooBarApplication") + .isMavenProject() + .hasStaticAndTemplatesResources(false).pomAssert() + .hasDependenciesCount(2) + .hasSpringBootStarterDependency("data-jpa") + .hasSpringBootStarterTest(); + } + + @Test + public void googleAnalytics() { + String body = htmlHome(); + assertTrue("google analytics should be enabled", body.contains("ga('create', 'UA-1234567-89', 'auto');")); + } + +} 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/project/MainControllerIntegrationTests.java new file mode 100644 index 00000000..3bf0dd22 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerIntegrationTests.java @@ -0,0 +1,458 @@ +/* + * Copyright 2012-2016 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 static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.json.JSONObject; +import org.junit.Ignore; +import org.junit.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; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.client.HttpClientErrorException; + +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import io.spring.initializr.web.mapper.InitializrMetadataVersion; + +/** + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +public class MainControllerIntegrationTests + extends AbstractInitializrControllerIntegrationTests { + + @Test + public void simpleZipProject() { + downloadZip("/starter.zip?style=web&style=jpa").isJavaProject() + .hasFile(".gitignore").isMavenProject() + .hasStaticAndTemplatesResources(true).pomAssert().hasDependenciesCount(3) + .hasSpringBootStarterDependency("web") + .hasSpringBootStarterDependency("data-jpa") // alias jpa -> data-jpa + .hasSpringBootStarterTest(); + } + + @Test + public void simpleTgzProject() { + downloadTgz("/starter.tgz?style=org.acme:foo").isJavaProject() + .hasFile(".gitignore").isMavenProject() + .hasStaticAndTemplatesResources(false).pomAssert().hasDependenciesCount(2) + .hasDependency("org.acme", "foo", "1.3.5"); + } + + @Test + public void dependencyInRange() { + Dependency biz = Dependency.create("org.acme", "biz", "1.3.5", "runtime"); + downloadTgz("/starter.tgz?style=org.acme:biz&bootVersion=1.2.1.RELEASE") + .isJavaProject().isMavenProject().hasStaticAndTemplatesResources(false) + .pomAssert().hasDependenciesCount(2).hasDependency(biz); + } + + @Test + public void dependencyNotInRange() { + try { + execute("/starter.tgz?style=org.acme:bur", byte[].class, null, (String[])null); + } + catch (HttpClientErrorException ex) { + assertEquals(HttpStatus.NOT_ACCEPTABLE, ex.getStatusCode()); + } + } + + @Test + public void noDependencyProject() { + downloadZip("/starter.zip").isJavaProject().isMavenProject() + .hasStaticAndTemplatesResources(false).pomAssert().hasDependenciesCount(2) + .hasSpringBootStarterRootDependency() // the root dep is added if none is + // specified + .hasSpringBootStarterTest(); + } + + @Test + public void dependenciesIsAnAliasOfStyle() { + downloadZip("/starter.zip?dependencies=web&dependencies=jpa").isJavaProject() + .isMavenProject().hasStaticAndTemplatesResources(true).pomAssert() + .hasDependenciesCount(3).hasSpringBootStarterDependency("web") + .hasSpringBootStarterDependency("data-jpa") // alias jpa -> data-jpa + .hasSpringBootStarterTest(); + } + + @Test + public void dependenciesIsAnAliasOfStyleCommaSeparated() { + downloadZip("/starter.zip?dependencies=web,jpa").isJavaProject().isMavenProject() + .hasStaticAndTemplatesResources(true).pomAssert().hasDependenciesCount(3) + .hasSpringBootStarterDependency("web") + .hasSpringBootStarterDependency("data-jpa") // alias jpa -> data-jpa + .hasSpringBootStarterTest(); + } + + @Test + public void gradleWarProject() { + downloadZip("/starter.zip?style=web&style=security&packaging=war&type=gradle.zip") + .isJavaWarProject().isGradleProject(); + } + + @Test + public void downloadCli() throws Exception { + assertSpringCliRedirect("/spring", "zip"); + } + + @Test + public void downloadCliAsZip() throws Exception { + assertSpringCliRedirect("/spring.zip", "zip"); + } + + @Test + public void downloadCliAsTarGz() throws Exception { + assertSpringCliRedirect("/spring.tar.gz", "tar.gz"); + } + + @Test + public 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); + assertEquals(HttpStatus.FOUND, entity.getStatusCode()); + String expected = "https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.1.4.RELEASE/spring-boot-cli-1.1.4.RELEASE-bin." + + extension; + assertEquals(new URI(expected), entity.getHeaders().getLocation()); + } + + @Test + public void metadataWithNoAcceptHeader() { + // rest template sets application/json by default + ResponseEntity response = invokeHome(null, "*/*"); + validateCurrentMetadata(response); + } + + @Test + @Ignore("Need a comparator that does not care about the number of elements in an array") + public void currentMetadataCompatibleWithV2() { + ResponseEntity response = invokeHome(null, "*/*"); + validateMetadata(response, + AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE, + "2.0.0", JSONCompareMode.LENIENT); + } + + @Test + public void metadataWithV2AcceptHeader() { + 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+json"); + validateMetadata(response, InitializrMetadataVersion.V2.getMediaType(), "2.0.0", + JSONCompareMode.STRICT); + } + + @Test + public void metadataWithCurrentAcceptHeader() { + ResponseEntity response = invokeHome(null, + "application/vnd.initializr.v2.1+json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + validateContentType(response, + AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(new JSONObject(response.getBody())); + } + + @Test + public void metadataWithSeveralAcceptHeader() { + ResponseEntity response = invokeHome(null, + "application/vnd.initializr.v2.1+json", + "application/vnd.initializr.v2+json"); + validateContentType(response, + AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(new JSONObject(response.getBody())); + } + + @Test + public void metadataWithHalAcceptHeader() { + ResponseEntity response = invokeHome(null, "application/hal+json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + validateContentType(response, MainController.HAL_JSON_CONTENT_TYPE); + validateCurrentMetadata(new JSONObject(response.getBody())); + } + + @Test + public void metadataWithUnknownAcceptHeader() { + try { + invokeHome(null, "application/vnd.initializr.v5.4+json"); + } + catch (HttpClientErrorException ex) { + assertEquals(HttpStatus.NOT_ACCEPTABLE, ex.getStatusCode()); + } + } + + @Test + public void curlReceivesTextByDefault() { + ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); + validateCurlHelpContent(response); + } + + @Test + public void curlCanStillDownloadZipArchive() { + ResponseEntity response = execute("/starter.zip", byte[].class, + "curl/1.2.4", "*/*"); + zipProjectAssert(response.getBody()).isMavenProject().isJavaProject(); + } + + @Test + public void curlCanStillDownloadTgzArchive() { + ResponseEntity response = execute("/starter.tgz", byte[].class, + "curl/1.2.4", "*/*"); + tgzProjectAssert(response.getBody()).isMavenProject().isJavaProject(); + } + + @Test + // make sure curl can still receive metadata with json + public void curlWithAcceptHeaderJson() { + ResponseEntity response = invokeHome("curl/1.2.4", "application/json"); + validateContentType(response, + AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(new JSONObject(response.getBody())); + } + + @Test + public void curlWithAcceptHeaderTextPlain() { + ResponseEntity response = invokeHome("curl/1.2.4", "text/plain"); + validateCurlHelpContent(response); + } + + @Test + public void unknownAgentReceivesJsonByDefault() { + ResponseEntity response = invokeHome("foo/1.0", "*/*"); + validateCurrentMetadata(response); + } + + @Test + public void httpieReceivesTextByDefault() { + ResponseEntity response = invokeHome("HTTPie/0.8.0", "*/*"); + validateHttpIeHelpContent(response); + } + + @Test + // make sure curl can still receive metadata with json + public void httpieWithAcceptHeaderJson() { + ResponseEntity response = invokeHome("HTTPie/0.8.0", "application/json"); + validateContentType(response, + AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(new JSONObject(response.getBody())); + ; + } + + @Test + public void httpieWithAcceptHeaderTextPlain() { + ResponseEntity response = invokeHome("HTTPie/0.8.0", "text/plain"); + validateHttpIeHelpContent(response); + } + + @Test + public void unknownCliWithTextPlain() { + ResponseEntity response = invokeHome(null, "text/plain"); + validateGenericHelpContent(response); + } + + @Test + public void springBootCliReceivesJsonByDefault() { + ResponseEntity response = invokeHome("SpringBootCli/1.2.0", "*/*"); + validateContentType(response, + AbstractInitializrControllerIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(new JSONObject(response.getBody())); + } + + @Test + public void springBootCliWithAcceptHeaderText() { + ResponseEntity response = invokeHome("SpringBootCli/1.2.0", "text/plain"); + validateSpringBootHelpContent(response); + } + + @Test + // Test that the current output is exactly what we expect + public void validateCurrentProjectMetadata() { + JSONObject json = getMetadataJson(); + validateCurrentMetadata(json); + } + + private void validateCurlHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + assertThat(response.getBody(), allOf( + containsString("Spring Initializr"), + containsString("Examples:"), + containsString("curl"))); + } + + private void validateHttpIeHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + assertThat(response.getBody(), allOf( + containsString("Spring Initializr"), + containsString("Examples:"), + not(containsString("curl")), + containsString("http"))); + } + + private void validateGenericHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + assertThat(response.getBody(), allOf( + containsString("Spring Initializr"), + not(containsString("Examples:")), + not(containsString("curl")))); + } + + private void validateSpringBootHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue())); + assertThat(response.getBody(), allOf( + containsString("Service capabilities"), + containsString("Supported dependencies"), + not(containsString("Examples:")), + not(containsString("curl")))); + } + + @Test + public void missingDependencyProperException() { + try { + downloadArchive("/starter.zip?style=foo:bar"); + fail("Should have failed"); + } catch (HttpClientErrorException ex) { + assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode()); + assertStandardErrorBody(ex.getResponseBodyAsString(), + "Unknown dependency 'foo:bar' check project metadata"); + } + } + + @Test + public void invalidDependencyProperException() { + try { + downloadArchive("/starter.zip?style=foo"); + fail("Should have failed"); + } catch (HttpClientErrorException ex) { + assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode()); + assertStandardErrorBody(ex.getResponseBodyAsString(), + "Unknown dependency 'foo' check project metadata"); + } + } + + @Test + public void homeIsForm() { + String body = htmlHome(); + assertTrue ("Wrong body:\n" + body, body.contains("action=\"/starter.zip\"")); + assertTrue ("Wrong body:\n" + body, body.contains("Web dependency description")); + assertFalse ("Wrong body:\n" + body, body.contains("${")); + assertFalse ("Wrong body:\n" + body, body.contains("{{")); + assertFalse ("Wrong body:\n" + body, body.contains("}}")); + assertTrue ("Wrong body:\n" + body, body.contains("