Expose service meta-data

InitializrMetadataBuilder can now merge a number of resources into a
single meta-data instance. A new `/metadata/service` endpoint is now
available and exposes the service meta-data of the current instance.

Combining those two, it is now possible to bootstrap an instance without
a single line of configuration; instead, the meta-data are built from the
content of a json document describing the service meta-data. This
document can be fetched remotely (via the new endpoint) or loaded from
a local file.

Each capability, including the InitializrConfiguration has now a `merge`
method with a "last wins" strategy. For collections, only unknown
elements are added.

Closes gh-88
This commit is contained in:
Stephane Nicoll
2015-03-03 14:29:31 +01:00
parent d0985d3cc5
commit d1a341799b
25 changed files with 981 additions and 58 deletions

View File

@@ -26,6 +26,10 @@ class InitializrConfiguration {
final Env env = new Env()
void merge(InitializrConfiguration other) {
env.merge(other.env)
}
/**
* Generate a suitable application mame based on the specified name. If no suitable
* application name can be generated from the specified {@code name}, the
@@ -116,5 +120,13 @@ class InitializrConfiguration {
this.artifactRepository = artifactRepository
}
void merge(Env other) {
artifactRepository = other.artifactRepository
springBootMetadataUrl = other.springBootMetadataUrl
fallbackApplicationName = other.fallbackApplicationName
invalidApplicationNames = other.invalidApplicationNames
forceSsl = other.forceSsl
}
}
}

View File

@@ -82,7 +82,7 @@ class InitializrAutoConfiguration {
@Bean
@ConditionalOnMissingBean
InitializrMetadata initializrMetadata(InitializrProperties properties) {
new InitializrMetadataBuilder().fromConfiguration(properties).build()
InitializrMetadataBuilder.fromInitializrProperties(properties).build()
}
@Bean

View File

@@ -66,7 +66,7 @@ class ProjectRequest {
* Initializes this instance with the defaults defined in the specified {@link InitializrMetadata}.
*/
void initialize(InitializrMetadata metadata) {
metadata.defaults().forEach { key, value ->
metadata.defaults().each { key, value ->
if (owner.hasProperty(key)) {
owner.setProperty(key, value)
}

View File

@@ -49,6 +49,20 @@ class DependenciesCapability extends ServiceCapability<List<DependencyGroup>> {
}
void validate() {
index()
}
@Override
void merge(List<DependencyGroup> otherContent) {
otherContent.each { group ->
if (!content.find { group.name.equals(it.name)}) {
content << group
}
}
index()
}
private void index() {
indexedDependencies.clear()
content.each { group ->
group.content.each { dependency ->

View File

@@ -41,22 +41,46 @@ class InitializrMetadata {
final SingleSelectCapability languages = new SingleSelectCapability('language')
final TextCapability name = new TextCapability('name', 'demo')
final TextCapability name = new TextCapability('name')
final TextCapability description = new TextCapability('description', 'Demo project for Spring Boot')
final TextCapability description = new TextCapability('description')
final TextCapability groupId = new TextCapability('groupId', 'org.test')
final TextCapability groupId = new TextCapability('groupId')
final TextCapability artifactId = new ArtifactIdCapability(name)
final TextCapability version = new TextCapability('version', '0.0.1-SNAPSHOT')
final TextCapability version = new TextCapability('version')
final TextCapability packageName = new PackageCapability(name)
InitializrMetadata(InitializrConfiguration configuration) {
InitializrMetadata() {
this(new InitializrConfiguration())
}
protected InitializrMetadata(InitializrConfiguration configuration) {
this.configuration = configuration
}
/**
* Merge this instance with the specified argument
* @param other
*/
void merge(InitializrMetadata other) {
this.configuration.merge(other.configuration)
this.dependencies.merge(other.dependencies)
this.types.merge(other.types)
this.bootVersions.merge(other.bootVersions)
this.packagings.merge(other.packagings)
this.javaVersions.merge(other.javaVersions)
this.languages.merge(other.languages)
this.name.merge(other.name)
this.description.merge(other.description)
this.groupId.merge(other.groupId)
this.artifactId.merge(other.artifactId)
this.version.merge(other.version)
this.packageName.merge(other.packageName)
}
/**
* Validate the meta-data.
*/

View File

@@ -16,10 +16,18 @@
package io.spring.initializr.metadata
import java.nio.charset.Charset
import com.fasterxml.jackson.databind.ObjectMapper
import groovy.util.logging.Log
import io.spring.initializr.InitializrConfiguration
import org.springframework.core.io.Resource
import org.springframework.util.StreamUtils
/**
* Builder for {@link InitializrMetadata}.
* Builder for {@link InitializrMetadata}. Allows to read meta-data from any arbitrary resource,
* including remote URLs.
*
* @author Stephane Nicoll
* @since 1.0
@@ -28,19 +36,60 @@ import io.spring.initializr.InitializrConfiguration
class InitializrMetadataBuilder {
private final List<InitializrMetadataCustomizer> customizers = []
private InitializrConfiguration configuration
private final InitializrConfiguration configuration
/**
* Adds the specified configuration.
* @see InitializrProperties
*/
InitializrMetadataBuilder fromConfiguration(InitializrProperties configuration) {
private InitializrMetadataBuilder(InitializrConfiguration configuration) {
this.configuration = configuration
withCustomizer(new InitializerPropertiesCustomizer(configuration))
}
/**
* Adds a {@link InitializrMetadataCustomizer}. customizers are invoked in their
* Create a builder instance from the specified {@link InitializrProperties}. Initialize
* the configuration to use.
* @see #withInitializrProperties(InitializrProperties)
*/
public static InitializrMetadataBuilder fromInitializrProperties(InitializrProperties configuration) {
new InitializrMetadataBuilder(configuration).withInitializrProperties(configuration)
}
/**
* Create an empty builder instance with a default {@link InitializrConfiguration}
*/
public static InitializrMetadataBuilder create() {
new InitializrMetadataBuilder(new InitializrConfiguration())
}
/**
* Add a {@link InitializrProperties} to be merged with other content. Merges the settings only
* and not the configuration.
* @see #withInitializrProperties(InitializrProperties, boolean)
*/
InitializrMetadataBuilder withInitializrProperties(InitializrProperties properties) {
withInitializrProperties(properties, false)
}
/**
* Add a {@link InitializrProperties} to be merged with other content.
* @param properties the settings to merge onto this instance
* @param mergeConfiguration specify if service configuration should be merged as well
*/
InitializrMetadataBuilder withInitializrProperties(InitializrProperties properties, boolean mergeConfiguration) {
if (mergeConfiguration) {
this.configuration.merge(properties)
}
withCustomizer(new InitializerPropertiesCustomizer(properties))
}
/**
* Add a {@link InitializrMetadata} to be merged with other content.
* @param resource a resource to a json document describing the meta-data to include
*/
InitializrMetadataBuilder withInitializrMetadata(Resource resource) {
withCustomizer(new ResourceInitializrMetadataCustomizer(resource))
}
/**
* Add a {@link InitializrMetadataCustomizer}. customizers are invoked in their
* order of addition.
* @see InitializrMetadataCustomizer
*/
@@ -54,12 +103,13 @@ class InitializrMetadataBuilder {
*/
InitializrMetadata build() {
InitializrConfiguration config = this.configuration ?: new InitializrConfiguration()
InitializrMetadata instance = createInstance(config)
InitializrMetadata metadata = createInstance(config)
for (InitializrMetadataCustomizer customizer : customizers) {
customizer.customize(instance)
customizer.customize(metadata)
}
instance.validate()
instance
applyDefaults(metadata)
metadata.validate()
metadata
}
/**
@@ -69,7 +119,25 @@ class InitializrMetadataBuilder {
new InitializrMetadata(configuration)
}
static class InitializerPropertiesCustomizer implements InitializrMetadataCustomizer {
/**
* Apply defaults to capabilities that have no value.
*/
protected applyDefaults(InitializrMetadata metadata) {
if (!metadata.name.content) {
metadata.name.content = 'demo'
}
if (!metadata.description.content) {
metadata.description.content = 'Demo project for Spring Boot'
}
if (!metadata.groupId.content) {
metadata.groupId.content = 'org.test'
}
if (!metadata.version.content) {
metadata.version.content = '0.0.1-SNAPSHOT'
}
}
private static class InitializerPropertiesCustomizer implements InitializrMetadataCustomizer {
private final InitializrProperties properties
@@ -78,21 +146,42 @@ class InitializrMetadataBuilder {
}
@Override
void customize(InitializrMetadata metadata) { // NICE: merge
metadata.dependencies.content.addAll(properties.dependencies)
metadata.types.content.addAll(properties.types)
metadata.bootVersions.content.addAll(properties.bootVersions)
metadata.packagings.content.addAll(properties.packagings)
metadata.javaVersions.content.addAll(properties.javaVersions)
metadata.languages.content.addAll(properties.languages)
metadata.groupId.content = properties.defaults.groupId
metadata.artifactId.content = properties.defaults.artifactId
metadata.version.content = properties.defaults.version
metadata.name.content = properties.defaults.name
metadata.description.content = properties.defaults.description
metadata.packageName.content = properties.defaults.packageName
void customize(InitializrMetadata metadata) {
metadata.dependencies.merge(properties.dependencies)
metadata.types.merge(properties.types)
metadata.bootVersions.merge(properties.bootVersions)
metadata.packagings.merge(properties.packagings)
metadata.javaVersions.merge(properties.javaVersions)
metadata.languages.merge(properties.languages)
metadata.groupId.merge(properties.defaults.groupId)
metadata.artifactId.merge(properties.defaults.artifactId)
metadata.version.merge(properties.defaults.version)
metadata.name.merge(properties.defaults.name)
metadata.description.merge(properties.defaults.description)
metadata.packageName.merge(properties.defaults.packageName)
}
}
@Log
private static class ResourceInitializrMetadataCustomizer implements InitializrMetadataCustomizer {
private static final Charset UTF_8 = Charset.forName('UTF-8')
private final Resource resource
ResourceInitializrMetadataCustomizer(Resource resource) {
this.resource = resource
}
@Override
void customize(InitializrMetadata metadata) {
log.info("Loading initializr meta-data from $resource")
def content = StreamUtils.copyToString(resource.getInputStream(), UTF_8)
ObjectMapper objectMapper = new ObjectMapper()
def anotherMetadata = objectMapper.readValue(content, InitializrMetadata)
metadata.merge(anotherMetadata)
}
}
}

View File

@@ -16,6 +16,7 @@
package io.spring.initializr.metadata
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import io.spring.initializr.InitializrConfiguration
import org.springframework.boot.context.properties.ConfigurationProperties
@@ -27,6 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties
* @since 1.0
*/
@ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false)
@JsonIgnoreProperties(["dependencies", "types", "packagings", "javaVersions", "languages", "bootVersions", "defaults"])
class InitializrProperties extends InitializrConfiguration {
final List<DependencyGroup> dependencies = []

View File

@@ -16,6 +16,10 @@
package io.spring.initializr.metadata
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import org.springframework.util.Assert
/**
* Defines a capability of the initializr service. Each capability
* is defined by a id and a {@link ServiceCapabilityType type}.
@@ -23,6 +27,7 @@ package io.spring.initializr.metadata
* @author Stephane Nicoll
* @since 1.0
*/
@JsonIgnoreProperties(["default", "all"])
abstract class ServiceCapability<T> {
final String id
@@ -42,5 +47,26 @@ abstract class ServiceCapability<T> {
*/
abstract T getContent()
/**
* Merge the content of this instance with the specified content.
* @see #merge(io.spring.initializr.metadata.ServiceCapability)
*/
abstract void merge(T otherContent)
/**
* Merge this capability with the specified argument. The service capabilities
* should match (i.e have the same {@code id} and {@code type}). Sub-classes
* may merge additional content.
*/
void merge(ServiceCapability<T> other) {
Assert.notNull(other, "Other must not be null")
Assert.state(this.id.equals(other.id))
Assert.state(this.type.equals(other.type))
if (other.description) {
this.description = other.description
}
merge(other.content)
}
}

View File

@@ -16,6 +16,9 @@
package io.spring.initializr.metadata
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
/**
* A {@link ServiceCapabilityType#SINGLE_SELECT single select} capability.
*
@@ -26,7 +29,8 @@ class SingleSelectCapability extends ServiceCapability<List<DefaultMetadataEleme
final List<DefaultMetadataElement> content = []
SingleSelectCapability(String id) {
@JsonCreator
SingleSelectCapability(@JsonProperty("id") String id) {
super(id, ServiceCapabilityType.SINGLE_SELECT)
}
@@ -37,4 +41,21 @@ class SingleSelectCapability extends ServiceCapability<List<DefaultMetadataEleme
return content.find { it.default }
}
/**
* Return the element with the specified id or {@code null} if no such
* element exists.
*/
DefaultMetadataElement get(String id) {
return content.find { id.equals(it.id)}
}
@Override
void merge(List<DefaultMetadataElement> otherContent) {
otherContent.each {
if (!get(it.id)) {
content << it
}
}
}
}

View File

@@ -16,6 +16,9 @@
package io.spring.initializr.metadata
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
/**
* A {@link ServiceCapabilityType#TEXT text} capability.
*
@@ -26,7 +29,8 @@ class TextCapability extends ServiceCapability<String> {
String content
TextCapability(String id) {
@JsonCreator
TextCapability(@JsonProperty("id") String id) {
super(id, ServiceCapabilityType.TEXT);
}
@@ -35,5 +39,12 @@ class TextCapability extends ServiceCapability<String> {
this.content = content
}
@Override
void merge(String otherContent) {
if (otherContent) {
this.content = otherContent
}
}
}

View File

@@ -45,4 +45,13 @@ class TypeCapability extends ServiceCapability<List<Type>> {
return content.find { it.default }
}
@Override
void merge(List<Type> otherContent) {
otherContent.each {
if (!get(it.id)) {
content << it
}
}
}
}

View File

@@ -24,6 +24,7 @@ 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.InitializrMetadata
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpHeaders
@@ -62,6 +63,12 @@ class MainController extends AbstractInitializrController {
request
}
@RequestMapping(value = "/metadata/service", produces = ["application/json"])
@ResponseBody
InitializrMetadata config() {
metadataProvider.get()
}
@RequestMapping(value = "/", produces = ["text/plain"])
ResponseEntity<String> serviceCapabilities(
@RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) {

View File

@@ -16,9 +16,8 @@
package io.spring.initializr.generator
import io.spring.initializr.InitializrConfiguration
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.InitializrMetadataBuilder
import io.spring.initializr.test.InitializrMetadataTestBuilder
import org.junit.Rule
import org.junit.Test
@@ -37,7 +36,7 @@ class ProjectRequestTests {
@Test
void initializeProjectRequest() {
def metadata = new InitializrMetadata(new InitializrConfiguration())
def metadata = InitializrMetadataBuilder.create().build()
metadata.groupId.content = 'org.acme'
metadata.artifactId.content = 'my-project'
ProjectRequest request = new ProjectRequest()

View File

@@ -20,6 +20,8 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.assertNull
import static org.junit.Assert.assertSame
@@ -83,6 +85,22 @@ class DependenciesCapabilityTest {
capability.validate()
}
@Test
void mergeAddEntry() {
DependenciesCapability capability = createDependenciesCapability('foo',
new Dependency(id: 'first'), new Dependency(id: 'second'))
DependenciesCapability anotherCapability = createDependenciesCapability('foo',
new Dependency(id: 'bar'), new Dependency(id: 'biz'))
anotherCapability.content << createDependencyGroup('bar', new Dependency(id: 'third'))
capability.merge(anotherCapability)
assertEquals 2, capability.content.size()
assertNotNull capability.get('first')
assertNotNull capability.get('second')
assertNotNull capability.get('third')
}
private static DependenciesCapability createDependenciesCapability(String groupName, Dependency... dependencies) {
DependenciesCapability capability = new DependenciesCapability()
DependencyGroup group = createDependencyGroup(groupName, dependencies)

View File

@@ -16,6 +16,7 @@
package io.spring.initializr.metadata
import io.spring.initializr.InitializrConfiguration
import org.junit.Test
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean
@@ -34,11 +35,92 @@ class InitializrMetadataBuilderTests {
@Test
void loadDefaultConfig() {
def bean = load(new ClassPathResource("application-test-default.yml"))
def metadata = new InitializrMetadataBuilder().fromConfiguration(bean).build()
def metadata = InitializrMetadataBuilder.fromInitializrProperties(bean).build()
assertDefaultConfig(metadata)
}
@Test
void mergeIdenticalConfig() {
def bean = load(new ClassPathResource("application-test-default.yml"))
def metadata = InitializrMetadataBuilder
.fromInitializrProperties(bean)
.withInitializrProperties(bean, true).build()
assertDefaultConfig(metadata)
}
@Test
void mergeConfig() {
def config = load(new ClassPathResource("application-test-default.yml"))
def customDefaultsConfig = load(new ClassPathResource("application-test-custom-defaults.yml"))
def metadata = InitializrMetadataBuilder
.fromInitializrProperties(config)
.withInitializrProperties(customDefaultsConfig).build()
assertDefaultConfig(metadata)
assertEquals 'org.foo', metadata.groupId.content
assertEquals 'foo-bar', metadata.artifactId.content
assertEquals '1.2.4-SNAPSHOT', metadata.version.content
assertEquals 'FooBar', metadata.name.content
assertEquals 'FooBar Project', metadata.description.content
assertEquals 'org.foo.demo', metadata.packageName.content
}
@Test
void mergeMetadata() {
def metadata = InitializrMetadataBuilder.create().withInitializrMetadata(
new ClassPathResource('metadata/service/test-min.json')).build()
assertEquals false, metadata.configuration.env.forceSsl
assertEquals 1, metadata.dependencies.content.size()
Dependency dependency = metadata.dependencies.get('test')
assertNotNull dependency
assertEquals 'org.springframework.boot', dependency.groupId
assertEquals 1, metadata.types.content.size()
assertEquals 2, metadata.bootVersions.content.size()
assertEquals 2, metadata.packagings.content.size()
assertEquals 1, metadata.javaVersions.content.size()
assertEquals 2, metadata.languages.content.size()
assertEquals 'meta-data-merge', metadata.name.content
assertEquals 'Demo project for meta-data merge', metadata.description.content
assertEquals 'org.acme', metadata.groupId.content
assertEquals 'meta-data', metadata.artifactId.content
assertEquals '1.0.0-SNAPSHOT', metadata.version.content
assertEquals 'org.acme.demo', metadata.packageName.content
}
@Test
void mergeConfigurationDisabledByDefault() {
def config = load(new ClassPathResource("application-test-default.yml"))
def customDefaultsConfig = load(new ClassPathResource("application-test-custom-env.yml"))
def metadata = InitializrMetadataBuilder
.fromInitializrProperties(config)
.withInitializrProperties(customDefaultsConfig).build()
InitializrConfiguration.Env defaultEnv = new InitializrConfiguration().env
InitializrConfiguration.Env actualEnv = metadata.configuration.env
assertEquals defaultEnv.artifactRepository, actualEnv.artifactRepository
assertEquals defaultEnv.springBootMetadataUrl, actualEnv.springBootMetadataUrl
assertEquals defaultEnv.fallbackApplicationName, actualEnv.fallbackApplicationName
assertEquals defaultEnv.forceSsl, actualEnv.forceSsl
}
@Test
void mergeConfiguration() {
def config = load(new ClassPathResource("application-test-default.yml"))
def customDefaultsConfig = load(new ClassPathResource("application-test-custom-env.yml"))
def metadata = InitializrMetadataBuilder
.fromInitializrProperties(config)
.withInitializrProperties(customDefaultsConfig, true).build()
InitializrConfiguration.Env defaultEnv = new InitializrConfiguration().env
InitializrConfiguration.Env actualEnv = metadata.configuration.env
assertEquals 'https://repo.spring.io/lib-release/', actualEnv.artifactRepository
assertEquals defaultEnv.springBootMetadataUrl, actualEnv.springBootMetadataUrl
assertEquals 'FooBarApplication', actualEnv.fallbackApplicationName
assertEquals false, actualEnv.forceSsl
}
private static assertDefaultConfig(InitializrMetadata metadata) {
assertNotNull metadata
assertEquals("Wrong number of dependencies", 9, metadata.dependencies.all.size())
assertEquals("Wrong number of dependency group", 2, metadata.dependencies.content.size())
assertEquals("Wrong number of types", 4, metadata.types.content.size())
assertEquals "Wrong number of dependencies", 9, metadata.dependencies.all.size()
assertEquals "Wrong number of dependency group", 2, metadata.dependencies.content.size()
assertEquals "Wrong number of types", 4, metadata.types.content.size()
}
@Test
@@ -46,7 +128,7 @@ class InitializrMetadataBuilderTests {
def group = new DependencyGroup(name: 'Extra')
def dependency = new Dependency(id: 'com.foo:foo:1.0.0')
group.content << dependency
def metadata = new InitializrMetadataBuilder().withCustomizer(new InitializrMetadataCustomizer() {
def metadata = InitializrMetadataBuilder.create().withCustomizer(new InitializrMetadataCustomizer() {
@Override
void customize(InitializrMetadata metadata) {
metadata.dependencies.content << group

View File

@@ -49,4 +49,20 @@ class SingleSelectCapabilityTests {
assertEquals second, capability.default
}
@Test
void mergeAddEntry() {
SingleSelectCapability capability = new SingleSelectCapability('test')
def foo = new DefaultMetadataElement(id: 'foo', default: false)
capability.content << foo
SingleSelectCapability anotherCapability = new SingleSelectCapability('test')
def bar = new DefaultMetadataElement(id: 'bar', default: false)
anotherCapability.content << bar
capability.merge(anotherCapability)
assertEquals 2, capability.content.size()
assertEquals foo, capability.get('foo')
assertEquals bar, capability.get('bar')
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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 org.junit.Test
import static org.junit.Assert.assertEquals
/**
* @author Stephane Nicoll
*/
class TextCapabilityTests {
@Test
void mergeContent() {
TextCapability capability = new TextCapability('foo', '1234')
capability.merge(new TextCapability('foo', '4567'))
assertEquals 'foo', capability.id
assertEquals ServiceCapabilityType.TEXT, capability.type
assertEquals '4567', capability.content
}
@Test
void mergeDescription() {
TextCapability capability = new TextCapability('foo', '1234')
def other = new TextCapability('foo', '')
other.description = 'my description'
capability.merge(other)
assertEquals 'foo', capability.id
assertEquals ServiceCapabilityType.TEXT, capability.type
assertEquals '1234', capability.content
assertEquals 'my description', capability.description
}
}

View File

@@ -48,4 +48,22 @@ class TypeCapabilityTests {
assertEquals second, capability.default
}
@Test
void mergeAddEntry() {
TypeCapability capability = new TypeCapability()
def foo = new Type(id: 'foo', default: false)
capability.content << foo
TypeCapability anotherCapability = new TypeCapability()
def foo2 =new Type(id: 'foo', default: true)
def bar =new Type(id: 'bar', default: true)
anotherCapability.content << foo2 << bar
capability.merge(anotherCapability)
assertEquals 2, capability.content.size()
assertEquals foo, capability.get('foo')
assertEquals bar, capability.get('bar')
assertEquals bar, capability.default
}
}

View File

@@ -16,8 +16,8 @@
package io.spring.initializr.support
import io.spring.initializr.InitializrConfiguration
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.InitializrMetadataBuilder
import org.junit.Test
import static org.junit.Assert.assertNotNull
@@ -28,7 +28,7 @@ import static org.junit.Assert.fail
*/
class SpringBootMetadataReaderTests {
private final InitializrMetadata metadata = new InitializrMetadata(new InitializrConfiguration())
private final InitializrMetadata metadata = InitializrMetadataBuilder.create().build()
@Test
void readAvailableVersions() {

View File

@@ -32,7 +32,7 @@ import io.spring.initializr.metadata.Type
*/
class InitializrMetadataTestBuilder {
private final InitializrMetadataBuilder builder = new InitializrMetadataBuilder()
private final InitializrMetadataBuilder builder = InitializrMetadataBuilder.create()
static InitializrMetadataTestBuilder withDefaults() {
new InitializrMetadataTestBuilder().addDefaults()

View File

@@ -16,11 +16,14 @@
package io.spring.initializr.web
import java.nio.charset.Charset
import io.spring.initializr.metadata.DefaultMetadataElement
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.support.DefaultInitializrMetadataProvider
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.test.ProjectAssert
import org.json.JSONObject
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
@@ -30,6 +33,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.IntegrationTest
import org.springframework.boot.test.SpringApplicationConfiguration
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
@@ -37,6 +41,7 @@ import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.util.StreamUtils
import org.springframework.web.client.RestTemplate
import static org.junit.Assert.assertEquals
@@ -157,6 +162,20 @@ abstract class AbstractInitializrControllerIntegrationTests {
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'))
// Let's parse the port as it is random
def content = json.replaceAll('@port@', String.valueOf(this.port))
new JSONObject(content)
} finally {
stream.close()
}
}
private enum ArchiveType {
ZIP,

View File

@@ -449,17 +449,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
}
private JSONObject readJson(String version) {
def resource = new ClassPathResource("metadata/test-default-$version" + ".json")
def stream = resource.inputStream
try {
def json = StreamUtils.copyToString(stream, Charset.forName('UTF-8'))
// Let's parse the port as it is random
def content = json.replaceAll('@port@', String.valueOf(this.port))
new JSONObject(content)
} finally {
stream.close()
}
readJsonFrom("metadata/test-default-$version" + ".json")
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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 io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.InitializrMetadataBuilder
import io.spring.initializr.metadata.InitializrMetadataProvider
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 AbstractInitializrControllerIntegrationTests {
@Autowired
private InitializrMetadataProvider metadataProvider
@Test
void initializeRemoteConfig() {
InitializrMetadata localMetadata = metadataProvider.get()
InitializrMetadata metadata = InitializrMetadataBuilder.create().withInitializrMetadata(
new UrlResource(createUrl('/metadata/service'))).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/service', String, null, 'text/plain')
} catch (HttpClientErrorException ex) {
assertEquals HttpStatus.NOT_ACCEPTABLE, ex.statusCode
}
}
@Test
void validateJson() {
ResponseEntity<String> response = execute('/metadata/service', String, null, 'application/json')
validateContentType(response, MediaType.APPLICATION_JSON)
JSONObject json = new JSONObject(response.body)
def expected = readJsonFrom("metadata/service/test-default.json")
JSONAssert.assertEquals(expected, json, JSONCompareMode.STRICT)
}
}

View File

@@ -0,0 +1,296 @@
{
"artifactId": {
"content": "demo",
"description": null,
"id": "artifactId",
"type": "TEXT"
},
"bootVersions": {
"content": [
{
"default": false,
"id": "1.2.0.BUILD-SNAPSHOT",
"name": "Latest SNAPSHOT"
},
{
"default": true,
"id": "1.1.4.RELEASE",
"name": "1.1.4"
},
{
"default": false,
"id": "1.0.2.RELEASE",
"name": "1.0.2"
}
],
"description": null,
"id": "bootVersion",
"type": "SINGLE_SELECT"
},
"configuration": {
"env": {
"artifactRepository": "https://repo.spring.io/release/",
"fallbackApplicationName": "Application",
"forceSsl": true,
"invalidApplicationNames": [
"SpringApplication",
"SpringBootApplication"
],
"springBootMetadataUrl": "https://spring.io/project_metadata/spring-boot"
}
},
"dependencies": {
"content": [
{
"content": [
{
"aliases": [],
"artifactId": "spring-boot-starter-web",
"description": "Web dependency description",
"facets": ["web"],
"groupId": "org.springframework.boot",
"id": "web",
"name": "Web",
"scope": "compile",
"version": null,
"versionRange": null
},
{
"aliases": [],
"artifactId": "spring-boot-starter-security",
"description": null,
"facets": [],
"groupId": "org.springframework.boot",
"id": "security",
"name": "Security",
"scope": "compile",
"version": null,
"versionRange": null
},
{
"aliases": ["jpa"],
"artifactId": "spring-boot-starter-data-jpa",
"description": null,
"facets": [],
"groupId": "org.springframework.boot",
"id": "data-jpa",
"name": "Data JPA",
"scope": "compile",
"version": null,
"versionRange": null
}
],
"name": "Core"
},
{
"content": [
{
"aliases": [],
"artifactId": "foo",
"description": null,
"facets": [],
"groupId": "org.acme",
"id": "org.acme:foo",
"name": "Foo",
"scope": "compile",
"version": "1.3.5",
"versionRange": null
},
{
"aliases": [],
"artifactId": "bar",
"description": null,
"facets": [],
"groupId": "org.acme",
"id": "org.acme:bar",
"name": "Bar",
"scope": "compile",
"version": "2.1.0",
"versionRange": null
},
{
"aliases": [],
"artifactId": "biz",
"description": null,
"facets": [],
"groupId": "org.acme",
"id": "org.acme:biz",
"name": "Biz",
"scope": "runtime",
"version": "1.3.5",
"versionRange": "1.2.0.BUILD-SNAPSHOT"
},
{
"aliases": [],
"artifactId": "bur",
"description": null,
"facets": [],
"groupId": "org.acme",
"id": "org.acme:bur",
"name": "Bur",
"scope": "test",
"version": "2.1.0",
"versionRange": "[1.1.4.RELEASE,1.2.0.BUILD-SNAPSHOT)"
},
{
"aliases": [],
"artifactId": "my-api",
"description": null,
"facets": [],
"groupId": "org.acme",
"id": "my-api",
"name": "My API",
"scope": "provided",
"version": null,
"versionRange": null
}
],
"name": "Other"
}
],
"description": null,
"id": "dependencies",
"type": "HIERARCHICAL_MULTI_SELECT"
},
"description": {
"content": "Demo project for Spring Boot",
"description": null,
"id": "description",
"type": "TEXT"
},
"groupId": {
"content": "org.test",
"description": null,
"id": "groupId",
"type": "TEXT"
},
"javaVersions": {
"content": [
{
"default": false,
"id": "1.6",
"name": "1.6"
},
{
"default": true,
"id": "1.7",
"name": "1.7"
},
{
"default": false,
"id": "1.8",
"name": "1.8"
}
],
"description": null,
"id": "javaVersion",
"type": "SINGLE_SELECT"
},
"languages": {
"content": [
{
"default": false,
"id": "groovy",
"name": "Groovy"
},
{
"default": true,
"id": "java",
"name": "Java"
}
],
"description": null,
"id": "language",
"type": "SINGLE_SELECT"
},
"name": {
"content": "demo",
"description": null,
"id": "name",
"type": "TEXT"
},
"packageName": {
"content": "demo",
"description": null,
"id": "packageName",
"type": "TEXT"
},
"packagings": {
"content": [
{
"default": true,
"id": "jar",
"name": "Jar"
},
{
"default": false,
"id": "war",
"name": "War"
}
],
"description": null,
"id": "packaging",
"type": "SINGLE_SELECT"
},
"types": {
"content": [
{
"action": "/pom.xml",
"default": false,
"description": null,
"id": "maven-build",
"name": "Maven POM",
"stsId": "pom.xml",
"tags": {
"build": "maven",
"format": "build"
}
},
{
"action": "/starter.zip",
"default": true,
"description": null,
"id": "maven-project",
"name": "Maven Project",
"stsId": "starter.zip",
"tags": {
"build": "maven",
"format": "project"
}
},
{
"action": "/build.gradle",
"default": false,
"description": null,
"id": "gradle-build",
"name": "Gradle Config",
"stsId": "build.gradle",
"tags": {
"build": "gradle",
"format": "build"
}
},
{
"action": "/starter.zip",
"default": false,
"description": null,
"id": "gradle-project",
"name": "Gradle Project",
"stsId": "gradle.zip",
"tags": {
"build": "gradle",
"format": "project"
}
}
],
"description": null,
"id": "type",
"type": "ACTION"
},
"version": {
"content": "0.0.1-SNAPSHOT",
"description": null,
"id": "version",
"type": "TEXT"
}
}

View File

@@ -0,0 +1,143 @@
{
"configuration": {
"env": {
"forceSsl": false
}
},
"dependencies": {
"id": "dependencies",
"type": "HIERARCHICAL_MULTI_SELECT",
"description": null,
"content": [
{
"name": "Core",
"content": [
{
"name": "test",
"id": "test",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot-starter-test"
}
]
}
]
},
"types": {
"id": "type",
"type": "ACTION",
"description": null,
"content": [
{
"name": "Maven POM",
"id": "maven-build",
"description": null,
"stsId": "pom.xml",
"action": "/pom.xml",
"tags": {
"build": "maven",
"format": "build"
},
"default": false
}
]
},
"bootVersions": {
"id": "bootVersion",
"type": "SINGLE_SELECT",
"description": null,
"content": [
{
"name": "1.1.4",
"id": "1.1.4.RELEASE",
"default": true
},
{
"name": "1.0.2",
"id": "1.0.2.RELEASE",
"default": false
}
]
},
"packagings": {
"id": "packaging",
"type": "SINGLE_SELECT",
"description": null,
"content": [
{
"name": "Jar",
"id": "jar",
"default": true
},
{
"name": "War",
"id": "war",
"default": false
}
]
},
"javaVersions": {
"id": "javaVersion",
"type": "SINGLE_SELECT",
"description": null,
"content": [
{
"name": "1.8",
"id": "1.8",
"default": true
}
]
},
"languages": {
"id": "language",
"type": "SINGLE_SELECT",
"description": null,
"content": [
{
"name": "Groovy",
"id": "groovy",
"default": false
},
{
"name": "Java",
"id": "java",
"default": true
}
]
},
"name": {
"id": "name",
"type": "TEXT",
"description": null,
"content": "meta-data-merge"
},
"description": {
"id": "description",
"type": "TEXT",
"description": null,
"content": "Demo project for meta-data merge"
},
"groupId": {
"id": "groupId",
"type": "TEXT",
"description": null,
"content": "org.acme"
},
"artifactId": {
"id": "artifactId",
"type": "TEXT",
"description": null,
"content": "meta-data"
},
"version": {
"id": "version",
"type": "TEXT",
"description": null,
"content": "1.0.0-SNAPSHOT"
},
"packageName": {
"id": "packageName",
"type": "TEXT",
"description": null,
"content": "org.acme.demo"
}
}