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() 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 * Generate a suitable application mame based on the specified name. If no suitable
* application name can be generated from the specified {@code name}, the * application name can be generated from the specified {@code name}, the
@@ -116,5 +120,13 @@ class InitializrConfiguration {
this.artifactRepository = artifactRepository 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 @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
InitializrMetadata initializrMetadata(InitializrProperties properties) { InitializrMetadata initializrMetadata(InitializrProperties properties) {
new InitializrMetadataBuilder().fromConfiguration(properties).build() InitializrMetadataBuilder.fromInitializrProperties(properties).build()
} }
@Bean @Bean

View File

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

View File

@@ -49,6 +49,20 @@ class DependenciesCapability extends ServiceCapability<List<DependencyGroup>> {
} }
void validate() { 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() indexedDependencies.clear()
content.each { group -> content.each { group ->
group.content.each { dependency -> group.content.each { dependency ->

View File

@@ -41,22 +41,46 @@ class InitializrMetadata {
final SingleSelectCapability languages = new SingleSelectCapability('language') 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 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) final TextCapability packageName = new PackageCapability(name)
InitializrMetadata(InitializrConfiguration configuration) { InitializrMetadata() {
this(new InitializrConfiguration())
}
protected InitializrMetadata(InitializrConfiguration configuration) {
this.configuration = 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. * Validate the meta-data.
*/ */

View File

@@ -16,10 +16,18 @@
package io.spring.initializr.metadata 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 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 * @author Stephane Nicoll
* @since 1.0 * @since 1.0
@@ -28,19 +36,60 @@ import io.spring.initializr.InitializrConfiguration
class InitializrMetadataBuilder { class InitializrMetadataBuilder {
private final List<InitializrMetadataCustomizer> customizers = [] private final List<InitializrMetadataCustomizer> customizers = []
private InitializrConfiguration configuration private final InitializrConfiguration configuration
/** private InitializrMetadataBuilder(InitializrConfiguration configuration) {
* Adds the specified configuration.
* @see InitializrProperties
*/
InitializrMetadataBuilder fromConfiguration(InitializrProperties configuration) {
this.configuration = 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. * order of addition.
* @see InitializrMetadataCustomizer * @see InitializrMetadataCustomizer
*/ */
@@ -54,12 +103,13 @@ class InitializrMetadataBuilder {
*/ */
InitializrMetadata build() { InitializrMetadata build() {
InitializrConfiguration config = this.configuration ?: new InitializrConfiguration() InitializrConfiguration config = this.configuration ?: new InitializrConfiguration()
InitializrMetadata instance = createInstance(config) InitializrMetadata metadata = createInstance(config)
for (InitializrMetadataCustomizer customizer : customizers) { for (InitializrMetadataCustomizer customizer : customizers) {
customizer.customize(instance) customizer.customize(metadata)
} }
instance.validate() applyDefaults(metadata)
instance metadata.validate()
metadata
} }
/** /**
@@ -69,7 +119,25 @@ class InitializrMetadataBuilder {
new InitializrMetadata(configuration) 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 private final InitializrProperties properties
@@ -78,21 +146,42 @@ class InitializrMetadataBuilder {
} }
@Override @Override
void customize(InitializrMetadata metadata) { // NICE: merge void customize(InitializrMetadata metadata) {
metadata.dependencies.content.addAll(properties.dependencies) metadata.dependencies.merge(properties.dependencies)
metadata.types.content.addAll(properties.types) metadata.types.merge(properties.types)
metadata.bootVersions.content.addAll(properties.bootVersions) metadata.bootVersions.merge(properties.bootVersions)
metadata.packagings.content.addAll(properties.packagings) metadata.packagings.merge(properties.packagings)
metadata.javaVersions.content.addAll(properties.javaVersions) metadata.javaVersions.merge(properties.javaVersions)
metadata.languages.content.addAll(properties.languages) metadata.languages.merge(properties.languages)
metadata.groupId.content = properties.defaults.groupId metadata.groupId.merge(properties.defaults.groupId)
metadata.artifactId.content = properties.defaults.artifactId metadata.artifactId.merge(properties.defaults.artifactId)
metadata.version.content = properties.defaults.version metadata.version.merge(properties.defaults.version)
metadata.name.content = properties.defaults.name metadata.name.merge(properties.defaults.name)
metadata.description.content = properties.defaults.description metadata.description.merge(properties.defaults.description)
metadata.packageName.content = properties.defaults.packageName 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 package io.spring.initializr.metadata
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import io.spring.initializr.InitializrConfiguration import io.spring.initializr.InitializrConfiguration
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
@@ -27,6 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties
* @since 1.0 * @since 1.0
*/ */
@ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false) @ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false)
@JsonIgnoreProperties(["dependencies", "types", "packagings", "javaVersions", "languages", "bootVersions", "defaults"])
class InitializrProperties extends InitializrConfiguration { class InitializrProperties extends InitializrConfiguration {
final List<DependencyGroup> dependencies = [] final List<DependencyGroup> dependencies = []

View File

@@ -16,6 +16,10 @@
package io.spring.initializr.metadata 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 * Defines a capability of the initializr service. Each capability
* is defined by a id and a {@link ServiceCapabilityType type}. * is defined by a id and a {@link ServiceCapabilityType type}.
@@ -23,6 +27,7 @@ package io.spring.initializr.metadata
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 1.0 * @since 1.0
*/ */
@JsonIgnoreProperties(["default", "all"])
abstract class ServiceCapability<T> { abstract class ServiceCapability<T> {
final String id final String id
@@ -42,5 +47,26 @@ abstract class ServiceCapability<T> {
*/ */
abstract T getContent() 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 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. * A {@link ServiceCapabilityType#SINGLE_SELECT single select} capability.
* *
@@ -26,7 +29,8 @@ class SingleSelectCapability extends ServiceCapability<List<DefaultMetadataEleme
final List<DefaultMetadataElement> content = [] final List<DefaultMetadataElement> content = []
SingleSelectCapability(String id) { @JsonCreator
SingleSelectCapability(@JsonProperty("id") String id) {
super(id, ServiceCapabilityType.SINGLE_SELECT) super(id, ServiceCapabilityType.SINGLE_SELECT)
} }
@@ -37,4 +41,21 @@ class SingleSelectCapability extends ServiceCapability<List<DefaultMetadataEleme
return content.find { it.default } 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 package io.spring.initializr.metadata
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
/** /**
* A {@link ServiceCapabilityType#TEXT text} capability. * A {@link ServiceCapabilityType#TEXT text} capability.
* *
@@ -26,7 +29,8 @@ class TextCapability extends ServiceCapability<String> {
String content String content
TextCapability(String id) { @JsonCreator
TextCapability(@JsonProperty("id") String id) {
super(id, ServiceCapabilityType.TEXT); super(id, ServiceCapabilityType.TEXT);
} }
@@ -35,5 +39,12 @@ class TextCapability extends ServiceCapability<String> {
this.content = content 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 } 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.mapper.InitializrMetadataVersion
import io.spring.initializr.generator.ProjectGenerator import io.spring.initializr.generator.ProjectGenerator
import io.spring.initializr.generator.ProjectRequest import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.InitializrMetadata
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
@@ -62,6 +63,12 @@ class MainController extends AbstractInitializrController {
request request
} }
@RequestMapping(value = "/metadata/service", produces = ["application/json"])
@ResponseBody
InitializrMetadata config() {
metadataProvider.get()
}
@RequestMapping(value = "/", produces = ["text/plain"]) @RequestMapping(value = "/", produces = ["text/plain"])
ResponseEntity<String> serviceCapabilities( ResponseEntity<String> serviceCapabilities(
@RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) { @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) {

View File

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

View File

@@ -20,6 +20,8 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException 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.assertNull
import static org.junit.Assert.assertSame import static org.junit.Assert.assertSame
@@ -83,6 +85,22 @@ class DependenciesCapabilityTest {
capability.validate() 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) { private static DependenciesCapability createDependenciesCapability(String groupName, Dependency... dependencies) {
DependenciesCapability capability = new DependenciesCapability() DependenciesCapability capability = new DependenciesCapability()
DependencyGroup group = createDependencyGroup(groupName, dependencies) DependencyGroup group = createDependencyGroup(groupName, dependencies)

View File

@@ -16,6 +16,7 @@
package io.spring.initializr.metadata package io.spring.initializr.metadata
import io.spring.initializr.InitializrConfiguration
import org.junit.Test import org.junit.Test
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean import org.springframework.beans.factory.config.YamlPropertiesFactoryBean
@@ -34,11 +35,92 @@ class InitializrMetadataBuilderTests {
@Test @Test
void loadDefaultConfig() { void loadDefaultConfig() {
def bean = load(new ClassPathResource("application-test-default.yml")) 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 assertNotNull metadata
assertEquals("Wrong number of dependencies", 9, metadata.dependencies.all.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 dependency group", 2, metadata.dependencies.content.size()
assertEquals("Wrong number of types", 4, metadata.types.content.size()) assertEquals "Wrong number of types", 4, metadata.types.content.size()
} }
@Test @Test
@@ -46,7 +128,7 @@ class InitializrMetadataBuilderTests {
def group = new DependencyGroup(name: 'Extra') def group = new DependencyGroup(name: 'Extra')
def dependency = new Dependency(id: 'com.foo:foo:1.0.0') def dependency = new Dependency(id: 'com.foo:foo:1.0.0')
group.content << dependency group.content << dependency
def metadata = new InitializrMetadataBuilder().withCustomizer(new InitializrMetadataCustomizer() { def metadata = InitializrMetadataBuilder.create().withCustomizer(new InitializrMetadataCustomizer() {
@Override @Override
void customize(InitializrMetadata metadata) { void customize(InitializrMetadata metadata) {
metadata.dependencies.content << group metadata.dependencies.content << group

View File

@@ -49,4 +49,20 @@ class SingleSelectCapabilityTests {
assertEquals second, capability.default 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 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 package io.spring.initializr.support
import io.spring.initializr.InitializrConfiguration
import io.spring.initializr.metadata.InitializrMetadata import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.InitializrMetadataBuilder
import org.junit.Test import org.junit.Test
import static org.junit.Assert.assertNotNull import static org.junit.Assert.assertNotNull
@@ -28,7 +28,7 @@ import static org.junit.Assert.fail
*/ */
class SpringBootMetadataReaderTests { class SpringBootMetadataReaderTests {
private final InitializrMetadata metadata = new InitializrMetadata(new InitializrConfiguration()) private final InitializrMetadata metadata = InitializrMetadataBuilder.create().build()
@Test @Test
void readAvailableVersions() { void readAvailableVersions() {

View File

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

View File

@@ -16,11 +16,14 @@
package io.spring.initializr.web package io.spring.initializr.web
import java.nio.charset.Charset
import io.spring.initializr.metadata.DefaultMetadataElement import io.spring.initializr.metadata.DefaultMetadataElement
import io.spring.initializr.metadata.InitializrMetadata import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.support.DefaultInitializrMetadataProvider import io.spring.initializr.support.DefaultInitializrMetadataProvider
import io.spring.initializr.metadata.InitializrMetadataProvider import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.test.ProjectAssert import io.spring.initializr.test.ProjectAssert
import org.json.JSONObject
import org.junit.Rule import org.junit.Rule
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith 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.IntegrationTest
import org.springframework.boot.test.SpringApplicationConfiguration import org.springframework.boot.test.SpringApplicationConfiguration
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.core.io.ClassPathResource
import org.springframework.http.HttpEntity import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod
@@ -37,6 +41,7 @@ import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import org.springframework.test.context.web.WebAppConfiguration import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.util.StreamUtils
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
import static org.junit.Assert.assertEquals import static org.junit.Assert.assertEquals
@@ -157,6 +162,20 @@ abstract class AbstractInitializrControllerIntegrationTests {
archiveFile 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 { private enum ArchiveType {
ZIP, ZIP,

View File

@@ -449,17 +449,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
} }
private JSONObject readJson(String version) { private JSONObject readJson(String version) {
def resource = new ClassPathResource("metadata/test-default-$version" + ".json") readJsonFrom("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()
}
} }
} }

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"
}
}