Expose dependencies meta-data

Register an additional endpoint at `/dependencies` that returns the
necessary information about the registered dependencies for a Spring
Boot version. If the `bootVersion` request parameter is not specified,
the default Spring Boot version is used.

The json content contains the artifact coordinates as well as the
necesasry BOM or repository to add. This information should be useful
enough for any tool to add additional dependencies to an existing
project or create one from scratch.

Closes gh-140
This commit is contained in:
Stephane Nicoll
2015-11-18 11:47:53 +00:00
parent 6a30dac6a6
commit 496520e990
26 changed files with 871 additions and 16 deletions

View File

@@ -22,9 +22,11 @@ import com.google.common.cache.CacheBuilder
import io.spring.initializr.generator.ProjectGenerationMetricsListener
import io.spring.initializr.generator.ProjectGenerator
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.support.DefaultDependencyMetadataProvider
import io.spring.initializr.support.DefaultInitializrMetadataProvider
import io.spring.initializr.web.MainController
import io.spring.initializr.web.UiController
@@ -67,19 +69,19 @@ class InitializrAutoConfiguration {
}
@Bean
@ConditionalOnMissingBean(MainController)
@ConditionalOnMissingBean
MainController initializrMainController() {
new MainController()
}
@Bean
@ConditionalOnMissingBean(UiController)
@ConditionalOnMissingBean
UiController initializrUiController() {
new UiController()
}
@Bean
@ConditionalOnMissingBean(ProjectGenerator)
@ConditionalOnMissingBean
ProjectGenerator projectGenerator() {
def generator = new ProjectGenerator()
generator.listeners << metricsListener()
@@ -98,17 +100,24 @@ class InitializrAutoConfiguration {
new DefaultInitializrMetadataProvider(metadata)
}
@Bean
@ConditionalOnMissingBean
DependencyMetadataProvider dependencyMetadataProvider() {
new DefaultDependencyMetadataProvider()
}
@Bean
ProjectGenerationMetricsListener metricsListener() {
new ProjectGenerationMetricsListener(counterService)
}
@Bean
@ConditionalOnMissingBean(CacheManager)
@ConditionalOnMissingBean
CacheManager cacheManager() {
def cacheManager = new SimpleCacheManager()
cacheManager.caches = Arrays.asList(
createConcurrentMapCache(600, 'initializr'),
new ConcurrentMapCache('dependency-metadata'),
new ConcurrentMapCache("project-resources"))
cacheManager
}

View File

@@ -0,0 +1,34 @@
/*
* 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.mapper
import io.spring.initializr.metadata.DependencyMetadata
/**
* Generate a JSON representation of a set of dependencies.
*
* @author Stephane Nicoll
* @since 1.0
*/
interface DependencyMetadataJsonMapper {
/**
* Write a json representation of the specified meta-data.
*/
String write(DependencyMetadata metadata);
}

View File

@@ -0,0 +1,85 @@
/*
* 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.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 meta-data format for v2.1.
*
* @author Stephane Nicoll
* @since 1.0
*/
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
}
}

View File

@@ -16,17 +16,37 @@
package io.spring.initializr.mapper
import org.springframework.hateoas.TemplateVariable
import org.springframework.hateoas.TemplateVariables
import org.springframework.hateoas.UriTemplate
/**
* A {@link InitializrMetadataJsonMapper} handling the meta-data format for v2.1
* <p>
* Version 2.1 brings the 'versionRange' attribute for a dependency to restrict
* the Spring Boot versions that can be used against it.
* the Spring Boot versions that can be used against it. That version also adds
* an additional `dependencies` endpoint.
*
* @author Stephane Nicoll
* @since 1.0
*/
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)
@@ -35,4 +55,13 @@ class InitializrMetadataV21JsonMapper extends InitializrMetadataV2JsonMapper {
}
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
}
}

View File

@@ -33,7 +33,8 @@ enum InitializrMetadataVersion {
/**
* Add 'versionRange' attribute to any dependency to specify which
* Spring Boot versions are compatible with it.
* Spring Boot versions are compatible with it. Also provide a
* separate 'dependencies' endpoint to query dependencies meta-data.
*/
V2_1('application/vnd.initializr.v2.1+json')

View File

@@ -29,7 +29,7 @@ import io.spring.initializr.util.VersionRange
* @author Stephane Nicoll
* @since 1.0
*/
@ToString(ignoreNulls = true, includePackage = false)
@ToString(ignoreNulls = true, excludes = 'mappings', includePackage = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
class BillOfMaterials {
@@ -79,6 +79,7 @@ class BillOfMaterials {
throw new IllegalStateException("No suitable mapping was found for $this and version $bootVersion")
}
@ToString(ignoreNulls = true, includePackage = false)
static class Mapping {
String versionRange

View File

@@ -179,6 +179,16 @@ class Dependency extends MetadataElement {
return this
}
/**
* Specify if this dependency is available for the specified Spring Boot version.
*/
boolean match(Version version) {
if (versionRange) {
return VersionRange.parse(versionRange).match(version)
}
true
}
/**
* Generate an id using the groupId and artifactId
*/

View File

@@ -0,0 +1,45 @@
/*
* 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.metadata
import io.spring.initializr.util.Version
/**
* Dependency meta-data for a given spring boot {@link Version}.
*
* @author Stephane Nicoll
* @since 1.0
*/
class DependencyMetadata {
final Version bootVersion
final Map<String, Dependency> dependencies
final Map<String, Repository> repositories
final Map<String, BillOfMaterials> boms
DependencyMetadata(Version bootVersion, Map<String, Dependency> dependencies,
Map<String, Repository> repositories, Map<String, BillOfMaterials> boms) {
this.bootVersion = bootVersion
this.dependencies = dependencies
this.repositories = repositories
this.boms = boms
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.metadata
import io.spring.initializr.util.Version
/**
* Provide the {@link DependencyMetadata} for a given spring boot version.
*
* @author Stephane Nicoll
*/
interface DependencyMetadataProvider {
/**
* Return the dependency metadata to use for the specified {@code bootVersion}.
*/
DependencyMetadata get(InitializrMetadata metadata, Version bootVersion)
}

View File

@@ -15,6 +15,7 @@
*/
package io.spring.initializr.metadata
/**
* Meta-data used to generate a project.
*
@@ -44,7 +45,7 @@ class InitializrMetadata {
final TextCapability name = new TextCapability('name', 'Name', 'project name (infer application name)')
final TextCapability description = new TextCapability('description', 'Description', 'project description' )
final TextCapability description = new TextCapability('description', 'Description', 'project description')
final TextCapability groupId = new TextCapability('groupId', 'Group', 'project coordinates')
@@ -89,18 +90,36 @@ class InitializrMetadata {
this.configuration.validate()
dependencies.validate()
for (Dependency dependency : dependencies.all) {
def repositories = configuration.env.repositories
dependencies.all.forEach { dependency ->
def boms = configuration.env.boms
if (dependency.bom && !boms[dependency.bom]) {
throw new InvalidInitializrMetadataException("Dependency $dependency " +
"defines an invalid BOM id $dependency.bom, available boms $boms")
}
def repositories = configuration.env.repositories
if (dependency.repository && !repositories[dependency.repository]) {
throw new InvalidInitializrMetadataException("Dependency $dependency " +
"defines an invalid repository id $dependency.repository, available repositores $repositories")
}
}
configuration.env.boms.values().forEach { bom ->
bom.repositories.forEach { r ->
if (!repositories[r]) {
throw new InvalidInitializrMetadataException("$bom " +
"defines an invalid repository id $r, available repositores $repositories")
}
}
bom.mappings.forEach{ m ->
m.repositories.forEach { r ->
if (!repositories[r]) {
throw new InvalidInitializrMetadataException("$m of $bom " +
"defines an invalid repository id $r, available repositores $repositories")
}
}
}
}
}
/**

View File

@@ -16,6 +16,9 @@
package io.spring.initializr.metadata
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Define a repository to be represented in the generated project
* if a dependency refers to it.
@@ -23,6 +26,8 @@ package io.spring.initializr.metadata
* @author Stephane Nicoll
* @since 1.0
*/
@EqualsAndHashCode
@ToString(includePackage = false)
class Repository {
String name

View File

@@ -0,0 +1,70 @@
/*
* 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.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
* @since 1.0
*/
class DefaultDependencyMetadataProvider implements DependencyMetadataProvider {
@Override
@Cacheable(cacheNames = "dependency-metadata", key = "#p1")
DependencyMetadata get(InitializrMetadata metadata, Version bootVersion) {
Map<String, Dependency> dependencies = [:]
metadata.dependencies.getAll().forEach { d ->
if (d.match(bootVersion)) {
dependencies[d.id] = d.resolve(bootVersion)
}
}
Map<String, Repository> repositories = [:]
dependencies.values().forEach { d ->
if (d.repository) {
repositories[d.repository] = metadata.configuration.env.repositories[d.repository]
}
}
Map<String, BillOfMaterials> boms = [:]
dependencies.values().forEach { d ->
if (d.bom) {
boms[d.bom] = metadata.configuration.env.boms.get(d.bom).resolve(bootVersion)
}
}
// Each resolved bom may require additional repositories
boms.values().forEach { b ->
b.repositories.forEach { id ->
repositories[id] = metadata.configuration.env.repositories[id]
}
}
return new DependencyMetadata(bootVersion, dependencies, repositories, boms)
}
}

View File

@@ -37,7 +37,8 @@ import org.springframework.util.Assert
* @since 1.0
*/
@EqualsAndHashCode
class Version implements Comparable<Version> {
@SuppressWarnings("serial")
final class Version implements Serializable, Comparable<Version> {
private static final String VERSION_REGEX = '^(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.([^0-9]+)(\\d+)?)?$'

View File

@@ -21,13 +21,16 @@ import java.util.concurrent.TimeUnit
import groovy.util.logging.Slf4j
import io.spring.initializr.generator.CommandLineHelpGenerator
import io.spring.initializr.generator.ProjectGenerator
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.mapper.DependencyMetadataV21JsonMapper
import io.spring.initializr.mapper.InitializrMetadataJsonMapper
import io.spring.initializr.mapper.InitializrMetadataV21JsonMapper
import io.spring.initializr.mapper.InitializrMetadataV2JsonMapper
import io.spring.initializr.mapper.InitializrMetadataVersion
import io.spring.initializr.generator.ProjectGenerator
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.DependencyMetadataProvider
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.util.Version
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.CacheControl
@@ -40,6 +43,7 @@ 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
/**
@@ -60,6 +64,9 @@ class MainController extends AbstractInitializrController {
@Autowired
private ProjectGenerator projectGenerator
@Autowired
private DependencyMetadataProvider dependencyMetadataProvider
private CommandLineHelpGenerator commandLineHelpGenerator = new CommandLineHelpGenerator()
@@ -140,6 +147,21 @@ class MainController extends AbstractInitializrController {
}
}
@RequestMapping(value = "/dependencies", produces = ["application/vnd.initializr.v2.1+json", "application/json"])
ResponseEntity<String> dependenciesV21(@RequestParam(required = false) String bootVersion) {
dependenciesFor(InitializrMetadataVersion.V2_1, bootVersion)
}
private ResponseEntity<String> 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() {

View File

@@ -0,0 +1,56 @@
/*
* 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.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
}
}

View File

@@ -68,6 +68,20 @@ class InitializrMetadataTests {
builder.build()
}
@Test
void invalidBomUnknownRepository() {
def bom = new BillOfMaterials(groupId: 'org.acme', artifactId: 'foo-bom',
version: '1.0.0.RELEASE', repositories: ['foo-repo'])
InitializrMetadataTestBuilder builder = InitializrMetadataTestBuilder
.withDefaults().addBom('foo-bom', bom)
thrown.expect(InvalidInitializrMetadataException)
thrown.expectMessage("invalid repository id foo-repo")
thrown.expectMessage("foo-bom")
builder.build()
}
@Test
void invalidBomVersionRangeMapping() {
def bom = new BillOfMaterials(groupId: 'org.acme', artifactId: 'foo-bom')
@@ -83,4 +97,20 @@ class InitializrMetadataTests {
builder.build()
}
@Test
void invalidBomVersionRangeMappingUnknownRepo() {
def bom = new BillOfMaterials(groupId: 'org.acme', artifactId: 'foo-bom')
bom.mappings << new BillOfMaterials.Mapping(versionRange: '[1.0.0.RELEASE,1.3.0.M1)', version: '1.0.0')
bom.mappings << new BillOfMaterials.Mapping(versionRange: '1.3.0.M2', version: '1.2.0', repositories: ['foo-repo'])
InitializrMetadataTestBuilder builder = InitializrMetadataTestBuilder
.withDefaults().addBom('foo-bom', bom)
thrown.expect(InvalidInitializrMetadataException)
thrown.expectMessage("invalid repository id foo-repo")
thrown.expectMessage('1.3.0.M2')
thrown.expectMessage("foo-bom")
builder.build()
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.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.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.versions << new Dependency.Mapping(versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)',
version: '0.1.0.RELEASE')
first.versions << new Dependency.Mapping(versionRange: '1.1.0.RELEASE',
version: '0.2.0.RELEASE')
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('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('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))
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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
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<String> response = execute('/dependencies', String, null, 'application/json')
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE)
validateDependenciesOutput('1.1.4', new JSONObject(response.body))
}
@Test
void filteredDependencies() {
ResponseEntity<String> response = execute('/dependencies?bootVersion=1.2.1.RELEASE',
String, null, 'application/json')
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG), not(nullValue()))
validateContentType(response, 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)
}
}

View File

@@ -46,7 +46,7 @@ class UiControllerIntegrationTests extends AbstractInitializrControllerIntegrati
}
protected void validateDependenciesOutput(String version, JSONObject actual) {
def expected = readJsonFrom("metadata/dependencies/test-dependencies-$version" + ".json")
def expected = readJsonFrom("metadata/ui/test-dependencies-$version" + ".json")
JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT)
}

View File

@@ -3,6 +3,25 @@ info:
version: 1.1.4.RELEASE
initializr:
env:
boms:
my-api-bom:
groupId: org.acme
artifactId: my-api-bom
mappings:
- versionRange: "[1.0.0.RELEASE,1.1.6.RELEASE)"
version: 1.0.0.RELEASE
repositories: my-api-repo-1
- versionRange: "1.2.1.RELEASE"
version: 2.0.0.RELEASE
repositories: my-api-repo-2
repositories:
my-api-repo-1:
name: repo1
url: http://example.com/repo1
my-api-repo-2:
name: repo2
url: http://example.com/repo2
dependencies:
- name: Core
content:
@@ -46,6 +65,7 @@ initializr:
groupId: org.acme
artifactId: my-api
scope: provided
bom: my-api-bom
types:
- name: Maven POM
id: maven-build

View File

@@ -31,7 +31,6 @@
},
"configuration": {"env": {
"artifactRepository": "https://repo.spring.io/release/",
"boms": {},
"fallbackApplicationName": "Application",
"forceSsl": true,
"invalidApplicationNames": [
@@ -39,6 +38,16 @@
"SpringBootApplication"
],
"repositories": {
"my-api-repo-1": {
"name": "repo1",
"url": "http://example.com/repo1",
"snapshotsEnabled": false
},
"my-api-repo-2": {
"name": "repo2",
"url": "http://example.com/repo2",
"snapshotsEnabled": false
},
"spring-milestones": {
"name": "Spring Milestones",
"snapshotsEnabled": false,
@@ -50,6 +59,29 @@
"url": "https://repo.spring.io/snapshot"
}
},
"boms": {
"my-api-bom": {
"groupId": "org.acme",
"artifactId": "my-api-bom",
"repositories": [],
"mappings": [
{
"versionRange": "[1.0.0.RELEASE,1.1.6.RELEASE)",
"repositories": [
"my-api-repo-1"
],
"version": "1.0.0.RELEASE"
},
{
"versionRange": "1.2.1.RELEASE",
"repositories": [
"my-api-repo-2"
],
"version": "2.0.0.RELEASE"
}
]
}
},
"springBootMetadataUrl": "https://spring.io/project_metadata/spring-boot"
}},
"dependencies": {
@@ -134,7 +166,8 @@
"groupId": "org.acme",
"id": "my-api",
"name": "My API",
"scope": "provided"
"scope": "provided",
"bom": "my-api-bom"
}
],
"name": "Other"

View File

@@ -0,0 +1,61 @@
{
"bootVersion": "1.1.4.RELEASE",
"repositories": {
"my-api-repo-1": {
"name": "repo1",
"url": "http://example.com/repo1",
"snapshotEnabled": false
}
},
"boms": {
"my-api-bom": {
"groupId": "org.acme",
"artifactId": "my-api-bom",
"version": "1.0.0.RELEASE",
"repositories": [
"my-api-repo-1"
]
}
},
"dependencies": {
"web": {
"groupId": "org.springframework.boot",
"scope": "compile",
"artifactId": "spring-boot-starter-web"
},
"security": {
"groupId": "org.springframework.boot",
"scope": "compile",
"artifactId": "spring-boot-starter-security"
},
"data-jpa": {
"groupId": "org.springframework.boot",
"scope": "compile",
"artifactId": "spring-boot-starter-data-jpa"
},
"org.acme:foo": {
"groupId": "org.acme",
"scope": "compile",
"artifactId": "foo",
"version": "1.3.5"
},
"org.acme:bar": {
"groupId": "org.acme",
"scope": "compile",
"artifactId": "bar",
"version": "2.1.0"
},
"org.acme:bur": {
"groupId": "org.acme",
"scope": "test",
"artifactId": "bur",
"version": "2.1.0"
},
"my-api": {
"groupId": "org.acme",
"scope": "provided",
"artifactId": "my-api",
"bom": "my-api-bom"
}
}
}

View File

@@ -0,0 +1,61 @@
{
"bootVersion": "1.2.1.RELEASE",
"repositories": {
"my-api-repo-2": {
"name": "repo2",
"url": "http://example.com/repo2",
"snapshotEnabled": false
}
},
"boms": {
"my-api-bom": {
"groupId": "org.acme",
"artifactId": "my-api-bom",
"version": "2.0.0.RELEASE",
"repositories": [
"my-api-repo-2"
]
}
},
"dependencies": {
"web": {
"groupId": "org.springframework.boot",
"scope": "compile",
"artifactId": "spring-boot-starter-web"
},
"security": {
"groupId": "org.springframework.boot",
"scope": "compile",
"artifactId": "spring-boot-starter-security"
},
"data-jpa": {
"groupId": "org.springframework.boot",
"scope": "compile",
"artifactId": "spring-boot-starter-data-jpa"
},
"org.acme:foo": {
"groupId": "org.acme",
"scope": "compile",
"artifactId": "foo",
"version": "1.3.5"
},
"org.acme:bar": {
"groupId": "org.acme",
"scope": "compile",
"artifactId": "bar",
"version": "2.1.0"
},
"org.acme:biz": {
"groupId": "org.acme",
"scope": "runtime",
"artifactId": "biz",
"version": "1.3.5"
},
"my-api": {
"groupId": "org.acme",
"scope": "provided",
"artifactId": "my-api",
"bom": "my-api-bom"
}
}
}

View File

@@ -1,5 +1,9 @@
{
"_links": {
"dependencies": {
"href": "https://localhost:@port@/dependencies{?bootVersion}",
"templated": true
},
"maven-build": {
"href": "https://localhost:@port@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
"templated": true