Redirect Spring cli distribution bundle

This commit adds the '/spring' endpoint that is used to download the
Spring CLI distribution bundle. Instead of relying on the presence of
a local 'spring.zip' file uploaded as part of the application, a
redirect to a configurable repository is used.

It is possible to download both the zip and the tar.gz distribution
by specifying the extension in the url (i.e. /spring.tar.gz provides
the tar.gz distribution.

Fixes gh-31
This commit is contained in:
Stephane Nicoll
2014-08-19 16:25:42 +02:00
parent 97df5393e9
commit 95441ef19c
11 changed files with 174 additions and 30 deletions

View File

@@ -71,11 +71,9 @@ An example Cloud Foundry `manifest.yml` file is provided. You should ensure that
the application name and URL (name and host values) are suitable for your environment the application name and URL (name and host values) are suitable for your environment
before running `cf push`. before running `cf push`.
You can jar up the app and make it executable in any environment. Care is needed with the includes and excludes: You can jar up the app and make it executable in any environment.
$ version=1.1.5.RELEASE $ spring jar start.jar app.groovy
$ wget -O spring.zip https://repo.spring.io/org/springframework/boot/spring-boot-cli/${version}/spring-boot-cli-${version}-bin.zip
$ spring jar --include '+spring.zip' start.jar app.groovy
To deploy on Cloudfoundry: To deploy on Cloudfoundry:

View File

@@ -21,6 +21,8 @@ import javax.annotation.PostConstruct
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude
import groovy.transform.ToString import groovy.transform.ToString
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
@@ -33,6 +35,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties
* <li>Supported Java versions</li> * <li>Supported Java versions</li>
* <li>Supported language</li> * <li>Supported language</li>
* <li>Supported Spring Boot versions</li> * <li>Supported Spring Boot versions</li>
* <li>Default settings used to generate the project</li>
* <li>Environment related settings</li>
* </ul> * </ul>
* *
* @author Stephane Nicoll * @author Stephane Nicoll
@@ -41,6 +45,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false) @ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false)
class InitializrMetadata { class InitializrMetadata {
private static final Logger logger = LoggerFactory.getLogger(InitializrMetadata)
final List<DependencyGroup> dependencies = new ArrayList<DependencyGroup>() final List<DependencyGroup> dependencies = new ArrayList<DependencyGroup>()
final List<Type> types = new ArrayList<Type>() final List<Type> types = new ArrayList<Type>()
@@ -55,6 +61,9 @@ class InitializrMetadata {
final Defaults defaults = new Defaults() final Defaults defaults = new Defaults()
@JsonIgnore
final Env env = new Env()
@JsonIgnore @JsonIgnore
final Map<String, Dependency> indexedDependencies = new HashMap<String, Dependency>() final Map<String, Dependency> indexedDependencies = new HashMap<String, Dependency>()
@@ -76,11 +85,6 @@ class InitializrMetadata {
request[key] = value request[key] = value
} }
} }
request.type = getDefault(types, request.type)
request.packaging = getDefault(packagings, request.packaging)
request.javaVersion = getDefault(javaVersions, request.javaVersion)
request.language = getDefault(languages, request.language)
request.bootVersion = getDefault(bootVersions, request.bootVersion)
request request
} }
@@ -98,6 +102,13 @@ class InitializrMetadata {
} }
} }
} }
env.validate()
defaults.type = getDefault(types)
defaults.packaging = getDefault(packagings)
defaults.javaVersion = getDefault(javaVersions)
defaults.language = getDefault(languages)
defaults.bootVersion = getDefault(bootVersions)
} }
private void indexDependency(String id, Dependency dependency) { private void indexDependency(String id, Dependency dependency) {
@@ -135,13 +146,14 @@ class InitializrMetadata {
} }
} }
static def getDefault(List elements, String defaultValue) { static def getDefault(List elements) {
for (DefaultIdentifiableElement element : elements) { for (DefaultIdentifiableElement element : elements) {
if (element.default) { if (element.default) {
return element.id return element.id
} }
} }
return defaultValue logger.warn('No default found amongst' + elements)
return (elements.isEmpty() ? null : elements.get(0).id)
} }
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
@@ -225,6 +237,11 @@ class InitializrMetadata {
String name = 'demo' String name = 'demo'
String description = 'Demo project for Spring Boot' String description = 'Demo project for Spring Boot'
String packageName String packageName
String type
String packaging
String javaVersion
String language
String bootVersion
/** /**
* Return the artifactId or the name of the project if none is set. * Return the artifactId or the name of the project if none is set.
@@ -242,6 +259,28 @@ class InitializrMetadata {
} }
/**
* Defines additional environment settings
*/
static class Env {
String artifactRepository = 'https://repo.spring.io/release/'
/**
* Create an URL suitable to download Spring Boot cli for the specified version and extension.
*/
String createCliDistributionURl(String version, String extension) {
artifactRepository + "org/springframework/boot/spring-boot-cli/$version/spring-boot-cli-$version-bin.$extension"
}
void validate() {
if (!artifactRepository.endsWith('/')) {
artifactRepository = artifactRepository + '/'
}
}
}
static class DefaultIdentifiableElement extends IdentifiableElement { static class DefaultIdentifiableElement extends IdentifiableElement {
@JsonIgnore @JsonIgnore

View File

@@ -74,6 +74,16 @@ class MainController {
template 'home.html', model template 'home.html', model
} }
@RequestMapping('/spring')
String spring() {
'redirect:' + metadata.env.createCliDistributionURl(metadata.defaults.bootVersion, 'zip')
}
@RequestMapping(value = ['/spring.tar.gz', 'spring.tgz'])
String springTgz() {
'redirect:' + metadata.env.createCliDistributionURl(metadata.defaults.bootVersion, 'tar.gz')
}
@RequestMapping('/pom') @RequestMapping('/pom')
@ResponseBody @ResponseBody
ResponseEntity<byte[]> pom(ProjectRequest request) { ResponseEntity<byte[]> pom(ProjectRequest request) {
@@ -124,4 +134,6 @@ class MainController {
result result
} }
} }

View File

@@ -120,7 +120,7 @@ class InitializrMetadataTests {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('foo', dependency, dependency2).get() .addDependencyGroup('foo', dependency, dependency2).validateAndGet()
assertSame dependency, metadata.getDependency('first') assertSame dependency, metadata.getDependency('first')
assertSame dependency2, metadata.getDependency('second') assertSame dependency2, metadata.getDependency('second')
@@ -137,7 +137,7 @@ class InitializrMetadataTests {
thrown.expect(IllegalArgumentException) thrown.expect(IllegalArgumentException)
thrown.expectMessage('conflict') thrown.expectMessage('conflict')
builder.get() builder.validateAndGet()
} }
@Test @Test
@@ -147,7 +147,7 @@ class InitializrMetadataTests {
dependency.aliases.add('alias2') dependency.aliases.add('alias2')
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('foo', dependency).get() .addDependencyGroup('foo', dependency).validateAndGet()
assertSame dependency, metadata.getDependency('first') assertSame dependency, metadata.getDependency('first')
assertSame dependency, metadata.getDependency('alias1') assertSame dependency, metadata.getDependency('alias1')
@@ -168,28 +168,42 @@ class InitializrMetadataTests {
thrown.expect(IllegalArgumentException) thrown.expect(IllegalArgumentException)
thrown.expectMessage('alias2') thrown.expectMessage('alias2')
builder.get() builder.validateAndGet()
} }
@Test @Test
void createProjectRequest() { void createProjectRequest() {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults().get() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults().validateAndGet()
ProjectRequest request = doCreateProjectRequest(metadata) ProjectRequest request = doCreateProjectRequest(metadata)
assertEquals metadata.defaults.groupId, request.groupId assertEquals metadata.defaults.groupId, request.groupId
} }
@Test
void validateArtifactRepository() {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults().instance()
metadata.env.artifactRepository = 'http://foo/bar'
metadata.validate()
assertEquals 'http://foo/bar/', metadata.env.artifactRepository
}
@Test @Test
void getDefaultNoDefault() { void getDefaultNoDefault() {
List elements = [] List elements = []
elements << createJavaVersion('one', false) << createJavaVersion('two', false) elements << createJavaVersion('one', false) << createJavaVersion('two', false)
assertEquals 'three', InitializrMetadata.getDefault(elements, 'three') assertEquals 'one', InitializrMetadata.getDefault(elements)
} }
@Test @Test
void getDefaultWithDefault() { void getDefaultEmpty() {
List elements = []
assertNull InitializrMetadata.getDefault(elements)
}
@Test
void getDefault() {
List elements = [] List elements = []
elements << createJavaVersion('one', false) << createJavaVersion('two', true) elements << createJavaVersion('one', false) << createJavaVersion('two', true)
assertEquals 'two', InitializrMetadata.getDefault(elements, 'three') assertEquals 'two', InitializrMetadata.getDefault(elements)
} }
private static ProjectRequest doCreateProjectRequest(InitializrMetadata metadata) { private static ProjectRequest doCreateProjectRequest(InitializrMetadata metadata) {

View File

@@ -31,7 +31,7 @@ class ProjectGeneratorTests {
@Before @Before
void setup() { void setup() {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('test', 'web', 'security', 'data-jpa', 'aop', 'batch', 'integration').get() .addDependencyGroup('test', 'web', 'security', 'data-jpa', 'aop', 'batch', 'integration').validateAndGet()
projectGenerator.metadata = metadata projectGenerator.metadata = metadata
} }
@@ -59,7 +59,7 @@ class ProjectGeneratorTests {
dependency.facets << 'web' dependency.facets << 'web'
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('core', 'web', 'security', 'data-jpa') .addDependencyGroup('core', 'web', 'security', 'data-jpa')
.addDependencyGroup('test', dependency).get() .addDependencyGroup('test', dependency).validateAndGet()
projectGenerator.metadata = metadata projectGenerator.metadata = metadata
ProjectRequest request = createProjectRequest('thymeleaf') ProjectRequest request = createProjectRequest('thymeleaf')
@@ -78,7 +78,7 @@ class ProjectGeneratorTests {
dependency.facets << 'web' dependency.facets << 'web'
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('core', 'web', 'security', 'data-jpa') .addDependencyGroup('core', 'web', 'security', 'data-jpa')
.addDependencyGroup('test', dependency).get() .addDependencyGroup('test', dependency).validateAndGet()
projectGenerator.metadata = metadata projectGenerator.metadata = metadata
ProjectRequest request = createProjectRequest('thymeleaf') ProjectRequest request = createProjectRequest('thymeleaf')

View File

@@ -35,7 +35,7 @@ class ProjectRequestTests {
void resolve() { void resolve() {
ProjectRequest request = new ProjectRequest() ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', 'web', 'security', 'spring-data').get() .addDependencyGroup('code', 'web', 'security', 'spring-data').validateAndGet()
request.style << 'web' << 'spring-data' request.style << 'web' << 'spring-data'
request.resolve(metadata) request.resolve(metadata)
@@ -47,7 +47,7 @@ class ProjectRequestTests {
void resolveFullMetadata() { void resolveFullMetadata() {
ProjectRequest request = new ProjectRequest() ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', createDependency('org.foo', 'acme', '1.2.0')).get() .addDependencyGroup('code', createDependency('org.foo', 'acme', '1.2.0')).validateAndGet()
request.style << 'org.foo:acme' request.style << 'org.foo:acme'
request.resolve(metadata) request.resolve(metadata)
assertDependency(request.dependencies.get(0), 'org.foo', 'acme', '1.2.0') assertDependency(request.dependencies.get(0), 'org.foo', 'acme', '1.2.0')
@@ -57,7 +57,7 @@ class ProjectRequestTests {
void resolveUnknownSimpleIdAsSpringBootStarter() { void resolveUnknownSimpleIdAsSpringBootStarter() {
ProjectRequest request = new ProjectRequest() ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', 'org.foo:bar').get() .addDependencyGroup('code', 'org.foo:bar').validateAndGet()
request.style << 'org.foo:bar' << 'foo-bar' request.style << 'org.foo:bar' << 'foo-bar'
request.resolve(metadata) request.resolve(metadata)
@@ -69,7 +69,7 @@ class ProjectRequestTests {
void resolveUnknownDependency() { void resolveUnknownDependency() {
ProjectRequest request = new ProjectRequest() ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults() InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', 'org.foo:bar').get() .addDependencyGroup('code', 'org.foo:bar').validateAndGet()
request.style << 'org.foo:acme' // does not exist and request.style << 'org.foo:acme' // does not exist and

View File

@@ -33,8 +33,12 @@ class InitializrMetadataBuilder {
new InitializrMetadataBuilder().addDefaults() new InitializrMetadataBuilder().addDefaults()
} }
InitializrMetadata get() { InitializrMetadata validateAndGet() {
metadata.validate() metadata.validate()
instance()
}
InitializrMetadata instance() {
metadata metadata
} }

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2014 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 org.junit.Test
import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
import org.springframework.test.context.ActiveProfiles
import static org.junit.Assert.assertEquals
/**
* @author Stephane Nicoll
*/
@ActiveProfiles(['test-default', 'test-custom-env'])
class MainControllerEnvIntegrationTests extends AbstractMainControllerIntegrationTests {
@Test
void downloadCliWithCustomRepository() {
HttpEntity entity = restTemplate.getForEntity(createUrl('/spring'), HttpEntity.class)
assertEquals HttpStatus.FOUND, entity.getStatusCode()
assertEquals new URI('https://repo.spring.io/lib-release/org/springframework/boot/spring-boot-cli/1.1.5.RELEASE' +
'/spring-boot-cli-1.1.5.RELEASE-bin.zip'), entity.getHeaders().getLocation()
}
}

View File

@@ -62,6 +62,34 @@ class MainControllerIntegrationTests extends AbstractMainControllerIntegrationTe
.isGradleProject() .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) {
ResponseEntity<?> entity = restTemplate.getForEntity(createUrl(context), ResponseEntity.class)
assertEquals HttpStatus.FOUND, entity.getStatusCode()
assertEquals new URI('https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.1.5.RELEASE' +
'/spring-boot-cli-1.1.5.RELEASE-bin.'+extension), entity.getHeaders().getLocation()
}
@Test // Test that the current output is exactly what we expect @Test // Test that the current output is exactly what we expect
void validateCurrentProjectMetadata() { void validateCurrentProjectMetadata() {
String json = restTemplate.getForObject(createUrl('/'), String.class) String json = restTemplate.getForObject(createUrl('/'), String.class)

View File

@@ -0,0 +1,3 @@
initializr:
env:
artifactRepository: https://repo.spring.io/lib-release

View File

@@ -114,6 +114,11 @@
"version": "0.0.1-SNAPSHOT", "version": "0.0.1-SNAPSHOT",
"name": "demo", "name": "demo",
"description": "Demo project for Spring Boot", "description": "Demo project for Spring Boot",
"packageName": "demo" "packageName": "demo",
"type": "starter.zip",
"packaging": "jar",
"javaVersion": "1.7",
"language": "java",
"bootVersion": "1.1.5.RELEASE"
} }
} }