Migrate initializr-actuator to Java

This commit is contained in:
Dave Syer
2017-01-26 14:21:24 +00:00
committed by Stephane Nicoll
parent fa485e3690
commit ad6430b92a
40 changed files with 2215 additions and 1838 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
.classpath
.project
.settings
.gitignore
.idea
.factorypath
*.iml

View File

@@ -75,6 +75,11 @@
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.info
import io.spring.initializr.metadata.InitializrMetadataProvider
import org.springframework.boot.actuate.info.Info
import org.springframework.boot.actuate.info.InfoContributor
/**
* An {@link InfoContributor} that exposes the actual ranges used by each bom
* defined in the project.
*
* @author Stephane Nicoll
*/
class BomRangesInfoContributor implements InfoContributor {
private final InitializrMetadataProvider metadataProvider
BomRangesInfoContributor(InitializrMetadataProvider metadataProvider) {
this.metadataProvider = metadataProvider
}
@Override
void contribute(Info.Builder builder) {
def details = [:]
metadataProvider.get().configuration.env.boms.each { k, v ->
if (v.mappings) {
def bom = [:]
v.mappings.each {
String requirement = "Spring Boot ${it.determineVersionRangeRequirement()}"
bom[it.version] = requirement
}
details[k] = bom
}
}
if (details) {
builder.withDetail('bom-ranges', details)
}
}
}

View File

@@ -1,132 +0,0 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.metric
import io.spring.initializr.generator.ProjectFailedEvent
import io.spring.initializr.generator.ProjectGeneratedEvent
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.util.Agent
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.actuate.metrics.CounterService
import org.springframework.context.event.EventListener
import org.springframework.util.StringUtils
/**
* A {@link ProjectGeneratedEvent} listener that uses a {@link CounterService} to update
* various project related metrics.
*
* @author Stephane Nicoll
*/
class ProjectGenerationMetricsListener {
private final CounterService counterService
@Autowired
ProjectGenerationMetricsListener(CounterService counterService) {
this.counterService = counterService
}
@EventListener
void onGeneratedProject(ProjectGeneratedEvent event) {
handleProjectRequest(event.projectRequest)
}
@EventListener
void onFailedProject(ProjectFailedEvent event) {
handleProjectRequest(event.projectRequest)
increment(key('failures'))
}
protected void handleProjectRequest(ProjectRequest request) {
increment(key('requests')) // Total number of requests
handleDependencies(request)
handleType(request)
handleJavaVersion(request)
handlePackaging(request)
handleLanguage(request)
handleBootVersion(request)
handleUserAgent(request)
}
protected void handleDependencies(ProjectRequest request) {
request.resolvedDependencies.each {
if (!ProjectRequest.DEFAULT_STARTER.equals(it.id)) {
def id = sanitize(it.id)
increment(key("dependency.$id"))
}
}
}
protected void handleType(ProjectRequest request) {
if (StringUtils.hasText(request.type)) {
def type = sanitize(request.type)
increment(key("type.$type"))
}
}
protected void handleJavaVersion(ProjectRequest request) {
if (StringUtils.hasText(request.javaVersion)) {
def javaVersion = sanitize(request.javaVersion)
increment(key("java_version.$javaVersion"))
}
}
protected void handlePackaging(ProjectRequest request) {
if (StringUtils.hasText(request.packaging)) {
def packaging = sanitize(request.packaging)
increment(key("packaging.$packaging"))
}
}
protected void handleLanguage(ProjectRequest request) {
if (StringUtils.hasText(request.language)) {
def language = sanitize(request.language)
increment(key("language.$language"))
}
}
protected void handleBootVersion(ProjectRequest request) {
if (StringUtils.hasText(request.bootVersion)) {
def bootVersion = sanitize(request.bootVersion)
increment(key("boot_version.$bootVersion"))
}
}
protected void handleUserAgent(ProjectRequest request) {
String userAgent = request.parameters['user-agent']
if (userAgent) {
Agent agent = Agent.fromUserAgent(userAgent)
if (agent) {
increment(key("client_id.$agent.id.id"))
}
}
}
protected void increment(String key) {
counterService.increment(key)
}
protected String key(String part) {
"initializr.$part"
}
protected String sanitize(String s) {
s.replace('.', '_')
}
}

View File

@@ -1,98 +0,0 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import groovy.util.logging.Slf4j
import io.spring.initializr.generator.ProjectRequestEvent
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.event.EventListener
import org.springframework.http.MediaType
import org.springframework.http.RequestEntity
import org.springframework.retry.RetryCallback
import org.springframework.retry.RetryContext
import org.springframework.retry.support.RetryTemplate
import org.springframework.scheduling.annotation.Async
import org.springframework.web.client.RestTemplate
/**
* Publish stats for each project generated to an Elastic index.
*
* @author Stephane Nicoll
*/
@Slf4j
class ProjectGenerationStatPublisher {
private final ProjectRequestDocumentFactory documentFactory
private final StatsProperties statsProperties
private final ObjectMapper objectMapper
private final RestTemplate restTemplate
private final RetryTemplate retryTemplate
ProjectGenerationStatPublisher(ProjectRequestDocumentFactory documentFactory,
StatsProperties statsProperties,
RetryTemplate retryTemplate) {
this.documentFactory = documentFactory
this.statsProperties = statsProperties
this.objectMapper = createObjectMapper()
this.restTemplate = new RestTemplateBuilder().basicAuthorization(
statsProperties.elastic.username, statsProperties.elastic.password).build()
this.retryTemplate = retryTemplate
}
@EventListener
@Async
void handleEvent(ProjectRequestEvent event) {
String json = null
try {
ProjectRequestDocument document = documentFactory.createDocument(event)
if (log.isDebugEnabled()) {
log.debug("Publishing $document")
}
json = toJson(document)
RequestEntity<String> request = RequestEntity
.post(this.statsProperties.elastic.entityUrl)
.contentType(MediaType.APPLICATION_JSON)
.body(json)
this.retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
@Override
Void doWithRetry(RetryContext context) {
restTemplate.exchange(request, String)
return null
}
})
} catch (Exception ex) {
log.warn(String.format(
"Failed to publish stat to index, document follows %n%n%s%n", json), ex)
}
}
private String toJson(ProjectRequestDocument stats) {
this.objectMapper.writeValueAsString(stats)
}
private static ObjectMapper createObjectMapper() {
def mapper = new ObjectMapper()
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
mapper
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
import groovy.transform.ToString
/**
* Define the statistics of a project generation.
*
* @author Stephane Nicoll
*/
@ToString(ignoreNulls = true, includePackage = false, includeNames = true)
class ProjectRequestDocument {
long generationTimestamp
String requestIp
String requestIpv4
String requestCountry
String clientId
String clientVersion
String groupId
String artifactId
String packageName
String bootVersion
String javaVersion
String language
String packaging
String type
final List<String> dependencies = []
String errorMessage
boolean invalid
boolean invalidJavaVersion
boolean invalidLanguage
boolean invalidPackaging
boolean invalidType
final List<String> invalidDependencies = []
}

View File

@@ -1,146 +0,0 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
import java.util.regex.Matcher
import java.util.regex.Pattern
import io.spring.initializr.generator.ProjectFailedEvent
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.generator.ProjectRequestEvent
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.Agent
/**
* Create {@link ProjectRequestDocument} instances.
*
* @author Stephane Nicoll
*/
class ProjectRequestDocumentFactory {
private static final IP_PATTERN = Pattern.compile("[0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*")
private final InitializrMetadataProvider metadataProvider
ProjectRequestDocumentFactory(InitializrMetadataProvider metadataProvider) {
this.metadataProvider = metadataProvider
}
ProjectRequestDocument createDocument(ProjectRequestEvent event) {
def metadata = metadataProvider.get()
def request = event.projectRequest
ProjectRequestDocument document = new ProjectRequestDocument()
document.generationTimestamp = event.timestamp
handleCloudFlareHeaders(request, document)
def candidate = request.parameters['x-forwarded-for']
if (!document.requestIp && candidate) {
document.requestIp = candidate
document.requestIpv4 = extractIpv4(candidate)
}
Agent agent = extractAgentInformation(request)
if (agent) {
document.clientId = agent.id.id
document.clientVersion = agent.version
}
document.groupId = request.groupId
document.artifactId = request.artifactId
document.packageName = request.packageName
document.bootVersion = request.bootVersion
document.javaVersion = request.javaVersion
if (request.javaVersion && !metadata.javaVersions.get(request.javaVersion)) {
document.invalid = true
document.invalidJavaVersion = true
}
document.language = request.language
if (request.language && !metadata.languages.get(request.language)) {
document.invalid = true
document.invalidLanguage = true
}
document.packaging = request.packaging
if (request.packaging && !metadata.packagings.get(request.packaging)) {
document.invalid = true
document.invalidPackaging = true
}
document.type = request.type
if (request.type && !metadata.types.get(request.type)) {
document.invalid = true
document.invalidType = true
}
// Let's not rely on the resolved dependencies here
def dependencies = []
dependencies.addAll(request.style)
dependencies.addAll(request.dependencies)
dependencies.each { id ->
if (metadata.dependencies.get(id)) {
document.dependencies << id
} else {
document.invalid = true
document.invalidDependencies << id
}
}
// Let's make sure that the document is flagged as invalid no matter what
if (event instanceof ProjectFailedEvent) {
document.invalid = true
if (event.cause) {
document.errorMessage = event.cause.message
}
}
document
}
private static void handleCloudFlareHeaders(ProjectRequest request, ProjectRequestDocument document) {
def candidate = request.parameters['cf-connecting-ip']
if (candidate) {
document.requestIp = candidate
document.requestIpv4 = extractIpv4(candidate)
}
String country = request.parameters['cf-ipcountry']
if (country && !country.toLowerCase().equals('xx')) {
document.requestCountry = country
}
}
private static Agent extractAgentInformation(ProjectRequest request) {
String userAgent = request.parameters['user-agent']
if (userAgent) {
return Agent.fromUserAgent(userAgent)
}
return null
}
private static String extractIpv4(def candidate) {
if (candidate) {
Matcher matcher = IP_PATTERN.matcher(candidate)
if (matcher.find()) {
return matcher.group()
}
}
return null
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.util.StringUtils
/**
* Statistics-related properties.
*
* @author Stephane Nicoll
*/
@ConfigurationProperties("initializr.stats")
class StatsProperties {
final Elastic elastic = new Elastic()
static final class Elastic {
/**
* Elastic service uri.
*/
String uri
/**
* Elastic service username.
*/
String username
/**
* Elastic service password
*/
String password
/**
* Name of the index.
*/
String indexName = 'initializr'
/**
* Name of the entity to use to publish stats.
*/
String entityName = 'request'
/**
* Number of attempts before giving up.
*/
int maxAttempts = 3
void setUri(String uri) {
this.uri = cleanUri(uri)
}
URI getEntityUrl() {
def string = "$uri/$indexName/$entityName"
new URI(string)
}
private static String cleanUri(String contextPath) {
if (StringUtils.hasText(contextPath) && contextPath.endsWith("/")) {
return contextPath.substring(0, contextPath.length() - 1)
}
return contextPath
}
}
}

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.autoconfigure
package io.spring.initializr.actuate.autoconfigure;
import io.spring.initializr.actuate.info.BomRangesInfoContributor
import io.spring.initializr.metadata.InitializrMetadataProvider
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import io.spring.initializr.actuate.info.BomRangesInfoContributor;
import io.spring.initializr.metadata.InitializrMetadataProvider;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -29,12 +29,12 @@ import org.springframework.context.annotation.Configuration
* @author Stephane Nicoll
*/
@Configuration
class InitializrActuatorEndpointsAutoConfiguration {
public class InitializrActuatorEndpointsAutoConfiguration {
@Bean
BomRangesInfoContributor bomRangesInfoContributor(
InitializrMetadataProvider metadataProvider) {
return new BomRangesInfoContributor(metadataProvider)
return new BomRangesInfoContributor(metadataProvider);
}
}

View File

@@ -14,27 +14,27 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.autoconfigure
package io.spring.initializr.actuate.autoconfigure;
import io.spring.initializr.actuate.metric.ProjectGenerationMetricsListener
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter;
import org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter
import org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration
import org.springframework.boot.actuate.metrics.CounterService
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository
import org.springframework.boot.actuate.metrics.writer.MetricWriter
import org.springframework.boot.autoconfigure.AutoConfigureAfter
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.util.ObjectUtils
import io.spring.initializr.actuate.metric.ProjectGenerationMetricsListener;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -43,39 +43,38 @@ import org.springframework.util.ObjectUtils
* @author Dave Syer
*/
@Configuration
@AutoConfigureAfter([RedisAutoConfiguration, MetricExportAutoConfiguration])
class InitializrMetricsConfiguration {
@AutoConfigureAfter({ RedisAutoConfiguration.class, MetricExportAutoConfiguration.class })
public class InitializrMetricsConfiguration {
@Bean
ProjectGenerationMetricsListener metricsListener(CounterService counterService) {
new ProjectGenerationMetricsListener(counterService)
return new ProjectGenerationMetricsListener(counterService);
}
@ConditionalOnBean(RedisConnectionFactory)
@ConditionalOnProperty(value = 'spring.metrics.export.enabled')
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnProperty(value = "spring.metrics.export.enabled")
@EnableScheduling
@EnableConfigurationProperties(MetricsProperties)
@EnableConfigurationProperties(MetricsProperties.class)
@Configuration
public static class MetricsExportConfiguration {
@Autowired
RedisConnectionFactory connectionFactory
RedisConnectionFactory connectionFactory;
@Autowired
MetricsProperties metrics
MetricsProperties metrics;
@Autowired
ApplicationContext context
ApplicationContext context;
@Bean
@ExportMetricWriter
MetricWriter writer() {
new RedisMetricRepository(connectionFactory,
metrics.prefix + metrics.getId(context.getId()) + '.'
+ ObjectUtils.getIdentityHexString(context) + '.',
metrics.key)
return new RedisMetricRepository(connectionFactory,
metrics.getPrefix() + metrics.getId(context.getId()) + "."
+ ObjectUtils.getIdentityHexString(context) + ".",
metrics.getKey());
}
}
}

View File

@@ -14,22 +14,24 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.autoconfigure
package io.spring.initializr.actuate.autoconfigure;
import io.spring.initializr.actuate.stat.ProjectGenerationStatPublisher
import io.spring.initializr.actuate.stat.ProjectRequestDocumentFactory
import io.spring.initializr.actuate.stat.StatsProperties
import io.spring.initializr.metadata.InitializrMetadataProvider
import java.util.Collections;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.retry.backoff.ExponentialBackOffPolicy
import org.springframework.retry.policy.SimpleRetryPolicy
import org.springframework.retry.support.RetryTemplate
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import io.spring.initializr.actuate.stat.ProjectGenerationStatPublisher;
import io.spring.initializr.actuate.stat.ProjectRequestDocumentFactory;
import io.spring.initializr.actuate.stat.StatsProperties;
import io.spring.initializr.metadata.InitializrMetadataProvider;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -38,29 +40,34 @@ import org.springframework.retry.support.RetryTemplate
* @author Stephane Nicoll
*/
@Configuration
@EnableConfigurationProperties(StatsProperties)
@ConditionalOnProperty('initializr.stats.elastic.uri')
@EnableConfigurationProperties(StatsProperties.class)
@ConditionalOnProperty("initializr.stats.elastic.uri")
class InitializrStatsAutoConfiguration {
@Autowired
private StatsProperties statsProperties
private StatsProperties statsProperties;
@Bean
ProjectGenerationStatPublisher projectRequestStatHandler(InitializrMetadataProvider provider) {
new ProjectGenerationStatPublisher(new ProjectRequestDocumentFactory(provider),
statsProperties, statsRetryTemplate())
ProjectGenerationStatPublisher projectRequestStatHandler(
InitializrMetadataProvider provider) {
return new ProjectGenerationStatPublisher(
new ProjectRequestDocumentFactory(provider), statsProperties,
statsRetryTemplate());
}
@Bean
@ConditionalOnMissingBean(name = "statsRetryTemplate")
RetryTemplate statsRetryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate()
def backOffPolicy = new ExponentialBackOffPolicy(initialInterval: 3000L, multiplier: 3)
def retryPolicy = new SimpleRetryPolicy(statsProperties.elastic.maxAttempts, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true))
retryTemplate.setBackOffPolicy(backOffPolicy)
retryTemplate.setRetryPolicy(retryPolicy)
retryTemplate
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(3000L);
backOffPolicy.setMultiplier(3);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(
statsProperties.getElastic().getMaxAttempts(), Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}

View File

@@ -14,53 +14,82 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.autoconfigure
package io.spring.initializr.actuate.autoconfigure;
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
/**
* Metrics-related configuration.
*
* @author Dave Syer
*/
@ConfigurationProperties('initializr.metrics')
class MetricsProperties {
@ConfigurationProperties("initializr.metrics")
public class MetricsProperties {
/**
* Prefix for redis keys holding metrics in data store.
*/
String prefix = 'spring.metrics.collector.'
private String prefix = "spring.metrics.collector.";
/**
* Redis key holding index to metrics keys in data store.
*/
String key = 'keys.spring.metrics.collector'
private String key = "keys.spring.metrics.collector";
/**
* Identifier for application in metrics keys. Keys will be exported in the form
* '[id].[hex].[name]' (where '[id]' is this value, '[hex]' is unique per application
* context, and '[name]' is the "natural" name for the metric.
* "[id].[hex].[name]" (where "[id]" is this value, "[hex]" is unique per application
* context, and "[name]" is the "natural" name for the metric.
*/
@Value('${spring.application.name:${vcap.application.name:application}}')
String id
@Value("${spring.application.name:${vcap.application.name:application}}")
private String id;
/**
* The rate (in milliseconds) at which metrics are exported to Redis. If the value is
* <=0 then the export is disabled.
*/
@Value('${spring.metrics.export.default.delayMillis:5000}')
long rateMillis = 5000L
@Value("${spring.metrics.export.default.delayMillis:5000}")
private long rateMillis = 5000L;
String getPrefix() {
if (prefix.endsWith('.')) {
return prefix
if (prefix.endsWith(".")) {
return prefix;
}
prefix + '.'
return prefix + ".";
}
String getId(String defaultValue) {
if (id) return id
defaultValue
if (StringUtils.hasText(id)) return id;
return defaultValue;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public long getRateMillis() {
return rateMillis;
}
public void setRateMillis(long rateMillis) {
this.rateMillis = rateMillis;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.info;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import io.spring.initializr.metadata.InitializrMetadataProvider;
/**
* An {@link InfoContributor} that exposes the actual ranges used by each bom
* defined in the project.
*
* @author Stephane Nicoll
*/
public class BomRangesInfoContributor implements InfoContributor {
private final InitializrMetadataProvider metadataProvider;
public BomRangesInfoContributor(InitializrMetadataProvider metadataProvider) {
this.metadataProvider = metadataProvider;
}
@Override
public void contribute(Info.Builder builder) {
Map<String, Object> details = new LinkedHashMap<>();
metadataProvider.get().getConfiguration().getEnv().getBoms().forEach( (k, v) -> {
if (v.getMappings()!=null && !v.getMappings().isEmpty()) {
Map<String, Object> bom = new LinkedHashMap<>();
v.getMappings().forEach(it -> {
String requirement = "Spring Boot " + it.determineVersionRangeRequirement();
bom.put(it.getVersion(), requirement);
});
details.put(k, bom);
}
});
if (!details.isEmpty()) {
builder.withDetail("bom-ranges", details);
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.metric;
import io.spring.initializr.generator.ProjectFailedEvent;
import io.spring.initializr.generator.ProjectGeneratedEvent;
import io.spring.initializr.generator.ProjectRequest;
import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.util.Agent;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.context.event.EventListener;
import org.springframework.util.StringUtils;
/**
* A {@link ProjectGeneratedEvent} listener that uses a {@link CounterService} to update
* various project related metrics.
*
* @author Stephane Nicoll
*/
public class ProjectGenerationMetricsListener {
private final CounterService counterService;
@Autowired
public ProjectGenerationMetricsListener(CounterService counterService) {
this.counterService = counterService;
}
@EventListener
public void onGeneratedProject(ProjectGeneratedEvent event) {
handleProjectRequest(event.getProjectRequest());
}
@EventListener
public void onFailedProject(ProjectFailedEvent event) {
handleProjectRequest(event.getProjectRequest());
increment(key("failures"));
}
protected void handleProjectRequest(ProjectRequest request) {
increment(key("requests"));// Total number of requests
handleDependencies(request);
handleType(request);
handleJavaVersion(request);
handlePackaging(request);
handleLanguage(request);
handleBootVersion(request);
handleUserAgent(request);
}
protected void handleDependencies(ProjectRequest request) {
List<Dependency> dependencies = request.getResolvedDependencies();
if (dependencies != null) {
dependencies.forEach(it -> {
if (!ProjectRequest.DEFAULT_STARTER.equals(it.getId())) {
String id = sanitize(it.getId());
increment(key("dependency." + id));
}
});
}
}
protected void handleType(ProjectRequest request) {
if (StringUtils.hasText(request.getType())) {
String type = sanitize(request.getType());
increment(key("type." + type));
}
}
protected void handleJavaVersion(ProjectRequest request) {
if (StringUtils.hasText(request.getJavaVersion())) {
String javaVersion = sanitize(request.getJavaVersion());
increment(key("java_version." + javaVersion));
}
}
protected void handlePackaging(ProjectRequest request) {
if (StringUtils.hasText(request.getPackaging())) {
String packaging = sanitize(request.getPackaging());
increment(key("packaging." + packaging));
}
}
protected void handleLanguage(ProjectRequest request) {
if (StringUtils.hasText(request.getLanguage())) {
String language = sanitize(request.getLanguage());
increment(key("language." + language));
}
}
protected void handleBootVersion(ProjectRequest request) {
if (StringUtils.hasText(request.getBootVersion())) {
String bootVersion = sanitize(request.getBootVersion());
increment(key("boot_version." + bootVersion));
}
}
protected void handleUserAgent(ProjectRequest request) {
String userAgent = (String) request.getParameters().get("user-agent");
if (userAgent != null) {
Agent agent = Agent.fromUserAgent(userAgent);
if (agent != null) {
increment(key("client_id." + agent.getId().getId()));
}
}
}
protected void increment(String key) {
counterService.increment(key);
}
protected String key(String part) {
return "initializr." + part;
}
protected String sanitize(String s) {
return s.replace(".", "_");
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.event.EventListener;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.initializr.generator.ProjectRequestEvent;
/**
* Publish stats for each project generated to an Elastic index.
*
* @author Stephane Nicoll
*/
public class ProjectGenerationStatPublisher {
private static final Logger log = LoggerFactory
.getLogger(ProjectGenerationStatPublisher.class);
private final ProjectRequestDocumentFactory documentFactory;
private final StatsProperties statsProperties;
private final ObjectMapper objectMapper;
private final RestTemplate restTemplate;
private final RetryTemplate retryTemplate;
public ProjectGenerationStatPublisher(ProjectRequestDocumentFactory documentFactory,
StatsProperties statsProperties, RetryTemplate retryTemplate) {
this.documentFactory = documentFactory;
this.statsProperties = statsProperties;
this.objectMapper = createObjectMapper();
this.restTemplate = new RestTemplateBuilder()
.basicAuthorization(statsProperties.getElastic().getUsername(),
statsProperties.getElastic().getPassword())
.build();
this.retryTemplate = retryTemplate;
}
@EventListener
@Async
public void handleEvent(ProjectRequestEvent event) {
String json = null;
try {
ProjectRequestDocument document = documentFactory.createDocument(event);
if (log.isDebugEnabled()) {
log.debug("Publishing $document");
}
json = toJson(document);
RequestEntity<String> request = RequestEntity
.post(this.statsProperties.getElastic().getEntityUrl())
.contentType(MediaType.APPLICATION_JSON).body(json);
this.retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
@Override
public Void doWithRetry(RetryContext context) {
restTemplate.exchange(request, String.class);
return null;
}
});
}
catch (Exception ex) {
log.warn(String.format(
"Failed to publish stat to index, document follows %n%n%s%n", json),
ex);
}
}
private String toJson(ProjectRequestDocument stats) {
try {
return this.objectMapper.writeValueAsString(stats);
}
catch (JsonProcessingException e) {
throw new IllegalStateException("Cannot convert to JSON", e);
}
}
private static ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
protected RestTemplate getRestTemplate() {
return this.restTemplate;
}
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat;
import java.util.ArrayList;
import java.util.List;
/**
* Define the statistics of a project generation.
*
* @author Stephane Nicoll
*/
public class ProjectRequestDocument {
private long generationTimestamp;
private String requestIp;
private String requestIpv4;
private String requestCountry;
private String clientId;
private String clientVersion;
private String groupId;
private String artifactId;
private String packageName;
private String bootVersion;
private String javaVersion;
private String language;
private String packaging;
private String type;
private final List<String> dependencies = new ArrayList<>();
private String errorMessage;
private boolean invalid;
private boolean invalidJavaVersion;
private boolean invalidLanguage;
private boolean invalidPackaging;
private boolean invalidType;
private final List<String> invalidDependencies = new ArrayList<>();
public long getGenerationTimestamp() {
return generationTimestamp;
}
public void setGenerationTimestamp(long generationTimestamp) {
this.generationTimestamp = generationTimestamp;
}
public String getRequestIp() {
return requestIp;
}
public void setRequestIp(String requestIp) {
this.requestIp = requestIp;
}
public String getRequestIpv4() {
return requestIpv4;
}
public void setRequestIpv4(String requestIpv4) {
this.requestIpv4 = requestIpv4;
}
public String getRequestCountry() {
return requestCountry;
}
public void setRequestCountry(String requestCountry) {
this.requestCountry = requestCountry;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientVersion() {
return clientVersion;
}
public void setClientVersion(String clientVersion) {
this.clientVersion = clientVersion;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getArtifactId() {
return artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getBootVersion() {
return bootVersion;
}
public void setBootVersion(String bootVersion) {
this.bootVersion = bootVersion;
}
public String getJavaVersion() {
return javaVersion;
}
public void setJavaVersion(String javaVersion) {
this.javaVersion = javaVersion;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getPackaging() {
return packaging;
}
public void setPackaging(String packaging) {
this.packaging = packaging;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public boolean isInvalid() {
return invalid;
}
public void setInvalid(boolean invalid) {
this.invalid = invalid;
}
public boolean isInvalidJavaVersion() {
return invalidJavaVersion;
}
public void setInvalidJavaVersion(boolean invalidJavaVersion) {
this.invalidJavaVersion = invalidJavaVersion;
}
public boolean isInvalidLanguage() {
return invalidLanguage;
}
public void setInvalidLanguage(boolean invalidLanguage) {
this.invalidLanguage = invalidLanguage;
}
public boolean isInvalidPackaging() {
return invalidPackaging;
}
public void setInvalidPackaging(boolean invalidPackaging) {
this.invalidPackaging = invalidPackaging;
}
public boolean isInvalidType() {
return invalidType;
}
public void setInvalidType(boolean invalidType) {
this.invalidType = invalidType;
}
public List<String> getDependencies() {
return dependencies;
}
public List<String> getInvalidDependencies() {
return invalidDependencies;
}
@Override
public String toString() {
return "ProjectRequestDocument [generationTimestamp=" + generationTimestamp + ", "
+ (requestIp != null ? "requestIp=" + requestIp + ", " : "")
+ (requestIpv4 != null ? "requestIpv4=" + requestIpv4 + ", " : "")
+ (requestCountry != null ? "requestCountry=" + requestCountry + ", "
: "")
+ (clientId != null ? "clientId=" + clientId + ", " : "")
+ (clientVersion != null ? "clientVersion=" + clientVersion + ", " : "")
+ (groupId != null ? "groupId=" + groupId + ", " : "")
+ (artifactId != null ? "artifactId=" + artifactId + ", " : "")
+ (packageName != null ? "packageName=" + packageName + ", " : "")
+ (bootVersion != null ? "bootVersion=" + bootVersion + ", " : "")
+ (javaVersion != null ? "javaVersion=" + javaVersion + ", " : "")
+ (language != null ? "language=" + language + ", " : "")
+ (packaging != null ? "packaging=" + packaging + ", " : "")
+ (type != null ? "type=" + type + ", " : "")
+ (dependencies != null ? "dependencies=" + dependencies + ", " : "")
+ (errorMessage != null ? "errorMessage=" + errorMessage + ", " : "")
+ "invalid=" + invalid + ", invalidJavaVersion=" + invalidJavaVersion
+ ", invalidLanguage=" + invalidLanguage + ", invalidPackaging="
+ invalidPackaging + ", invalidType=" + invalidType + ", "
+ (invalidDependencies != null
? "invalidDependencies=" + invalidDependencies : "")
+ "]";
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.StringUtils;
import io.spring.initializr.generator.ProjectFailedEvent;
import io.spring.initializr.generator.ProjectRequest;
import io.spring.initializr.generator.ProjectRequestEvent;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.util.Agent;
/**
* Create {@link ProjectRequestDocument} instances.
*
* @author Stephane Nicoll
*/
public class ProjectRequestDocumentFactory {
private static final Pattern IP_PATTERN = Pattern.compile("[0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*");
private final InitializrMetadataProvider metadataProvider;
public ProjectRequestDocumentFactory(InitializrMetadataProvider metadataProvider) {
this.metadataProvider = metadataProvider;
}
public ProjectRequestDocument createDocument(ProjectRequestEvent event) {
InitializrMetadata metadata = metadataProvider.get();
ProjectRequest request = event.getProjectRequest();
ProjectRequestDocument document = new ProjectRequestDocument();
document.setGenerationTimestamp(event.getTimestamp());
handleCloudFlareHeaders(request, document);
String candidate = (String) request.getParameters().get("x-forwarded-for");
if (!StringUtils.hasText(document.getRequestIp()) && candidate!=null) {
document.setRequestIp(candidate);
document.setRequestIpv4(extractIpv4(candidate));
}
Agent agent = extractAgentInformation(request);
if (agent!=null) {
document.setClientId(agent.getId().getId());
document.setClientVersion(agent.getVersion());
}
document.setGroupId(request.getGroupId());
document.setArtifactId(request.getArtifactId());
document.setPackageName(request.getPackageName());
document.setBootVersion(request.getBootVersion());
document.setJavaVersion(request.getJavaVersion());
if (StringUtils.hasText(request.getJavaVersion()) && metadata.getJavaVersions().get(request.getJavaVersion())==null) {
document.setInvalid(true);
document.setInvalidJavaVersion(true);
}
document.setLanguage(request.getLanguage());
if (StringUtils.hasText(request.getLanguage()) && metadata.getLanguages().get(request.getLanguage())==null) {
document.setInvalid(true);
document.setInvalidLanguage(true);
}
document.setPackaging(request.getPackaging());
if (StringUtils.hasText(request.getPackaging()) && metadata.getPackagings().get(request.getPackaging())==null) {
document.setInvalid(true);
document.setInvalidPackaging(true);
}
document.setType(request.getType());
if (StringUtils.hasText(request.getType()) && metadata.getTypes().get(request.getType())==null) {
document.setInvalid(true);
document.setInvalidType(true);
}
// Let's not rely on the resolved dependencies here
List<String> dependencies = new ArrayList<>();
dependencies.addAll(request.getStyle());
dependencies.addAll(request.getDependencies());
dependencies.forEach( id -> {
if (metadata.getDependencies().get(id)!=null) {
document.getDependencies().add(id);
} else {
document.setInvalid(true);
document.getInvalidDependencies().add(id);
}
});
// Let's make sure that the document is flagged as invalid no matter what
if (event instanceof ProjectFailedEvent) {
ProjectFailedEvent failed = (ProjectFailedEvent) event;
document.setInvalid(true);
if (failed.getCause()!=null) {
document.setErrorMessage(failed.getCause().getMessage());
}
}
return document;
}
private static void handleCloudFlareHeaders(ProjectRequest request, ProjectRequestDocument document) {
String candidate = (String) request.getParameters().get("cf-connecting-ip");
if (StringUtils.hasText(candidate)) {
document.setRequestIp(candidate);
document.setRequestIpv4(extractIpv4(candidate));
}
String country = (String) request.getParameters().get("cf-ipcountry");
if (StringUtils.hasText(country) && !country.toLowerCase().equals("xx")) {
document.setRequestCountry(country);
}
}
private static Agent extractAgentInformation(ProjectRequest request) {
String userAgent = (String) request.getParameters().get("user-agent");
if (StringUtils.hasText(userAgent)) {
return Agent.fromUserAgent(userAgent);
}
return null;
}
private static String extractIpv4(String candidate) {
if (StringUtils.hasText(candidate)) {
Matcher matcher = IP_PATTERN.matcher(candidate);
if (matcher.find()) {
return matcher.group();
}
}
return null;
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
/**
* Statistics-related properties.
*
* @author Stephane Nicoll
*/
@ConfigurationProperties("initializr.stats")
public class StatsProperties {
private final Elastic elastic = new Elastic();
public Elastic getElastic() {
return elastic;
}
public static final class Elastic {
/**
* Elastic service uri.
*/
private String uri;
/**
* Elastic service username.
*/
private String username;
/**
* Elastic service password
*/
private String password;
/**
* Name of the index.
*/
private String indexName = "initializr";
/**
* Name of the entity to use to publish stats.
*/
private String entityName = "request";
/**
* Number of attempts before giving up.
*/
private int maxAttempts = 3;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getIndexName() {
return indexName;
}
public void setIndexName(String indexName) {
this.indexName = indexName;
}
public String getEntityName() {
return entityName;
}
public void setEntityName(String entityName) {
this.entityName = entityName;
}
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = cleanUri(uri);
}
public URI getEntityUrl() {
String string = uri + "/" + indexName + "/" + entityName;
try {
return new URI(string);
}
catch (URISyntaxException e) {
throw new IllegalStateException("Cannot create entity URL: " + string, e);
}
}
private static String cleanUri(String contextPath) {
if (StringUtils.hasText(contextPath) && contextPath.endsWith("/")) {
return contextPath.substring(0, contextPath.length() - 1);
}
return contextPath;
}
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate
import groovy.json.JsonSlurper
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests
import org.junit.Test
import org.springframework.test.context.ActiveProfiles
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertTrue
/**
* Tests for actuator specific features.
*
* @author Stephane Nicoll
*/
@ActiveProfiles('test-default')
class ActuatorIntegrationTests extends AbstractInitializrControllerIntegrationTests {
private final def slurper = new JsonSlurper()
@Test
void infoHasExternalProperties() {
def body = restTemplate.getForObject(createUrl('/info'), String)
assertTrue("Wrong body:\n$body", body.contains('"spring-boot"'))
assertTrue("Wrong body:\n$body", body.contains('"version":"1.1.4.RELEASE"'))
}
@Test
void metricsAvailableByDefault() {
downloadZip('/starter.zip?packaging=jar&javaVersion=1.8&style=web&style=jpa')
def result = metricsEndpoint()
def requests = result['counter.initializr.requests']
def packaging = result['counter.initializr.packaging.jar']
def javaVersion = result['counter.initializr.java_version.1_8']
def webDependency = result['counter.initializr.dependency.web']
def jpaDependency = result['counter.initializr.dependency.jpa']
downloadZip('/starter.zip?packaging=jar&javaVersion=1.8&style=web') // No jpa dep this time
def updatedResult = metricsEndpoint()
assertEquals 'Number of request should have increased',
requests + 1, updatedResult['counter.initializr.requests']
assertEquals 'jar packaging metric should have increased',
packaging + 1, updatedResult['counter.initializr.packaging.jar']
assertEquals 'java version metric should have increased',
javaVersion + 1, updatedResult['counter.initializr.java_version.1_8']
assertEquals 'web dependency metric should have increased',
webDependency + 1, updatedResult['counter.initializr.dependency.web']
assertEquals 'jpa dependency metric should not have increased',
jpaDependency, updatedResult['counter.initializr.dependency.jpa']
}
private def metricsEndpoint() {
parseJson(restTemplate.getForObject(createUrl('/metrics'), String))
}
private def parseJson(String content) {
slurper.parseText(content)
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.info
import io.spring.initializr.metadata.BillOfMaterials
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import org.junit.Test
import org.springframework.boot.actuate.info.Info
import static org.assertj.core.api.Assertions.assertThat
import static org.assertj.core.api.Assertions.entry
/**
* Tests for {@link BomRangesInfoContributor}
*
* @author Stephane Nicoll
*/
class BomRangesInfoContributorTests {
@Test
void noBom() {
def metadata = InitializrMetadataTestBuilder.withDefaults().build()
def info = getInfo(metadata)
assertThat(info.details).doesNotContainKeys('bom-ranges')
}
@Test
void noMapping() {
def bom = new BillOfMaterials(groupId: 'com.example', artifactId: 'bom', version: '1.0.0')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addBom('foo', bom)
.build()
def info = getInfo(metadata)
assertThat(info.details).doesNotContainKeys('bom-ranges')
}
@Test
void withMappings() {
BillOfMaterials bom = new BillOfMaterials(groupId: 'com.example',
artifactId: 'bom', version: '1.0.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '[1.3.0.RELEASE,1.3.8.RELEASE]', version: '1.1.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '1.3.8.BUILD-SNAPSHOT', version: '1.1.1-SNAPSHOT')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addBom('foo', bom)
.build()
def info = getInfo(metadata)
assertThat(info.details).containsKeys('bom-ranges')
Map<String,Object> ranges = info.details['bom-ranges'] as Map<String, Object>
assertThat(ranges).containsOnlyKeys('foo')
Map<String,Object> foo = ranges['foo'] as Map<String, Object>
assertThat(foo).containsExactly(
entry('1.1.0', 'Spring Boot >=1.3.0.RELEASE and <=1.3.8.RELEASE'),
entry('1.1.1-SNAPSHOT', 'Spring Boot >=1.3.8.BUILD-SNAPSHOT'))
}
private static Info getInfo(InitializrMetadata metadata) {
Info.Builder builder = new Info.Builder()
new BomRangesInfoContributor(new SimpleInitializrMetadataProvider(metadata))
.contribute(builder)
builder.build()
}
}

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.metric
import io.spring.initializr.actuate.test.RedisRunning
import io.spring.initializr.generator.ProjectGeneratedEvent
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.InitializrMetadataBuilder
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.metadata.InitializrProperties
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository
import org.springframework.boot.actuate.metrics.writer.MetricWriter
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Bean
import org.springframework.test.context.junit4.SpringRunner
import static org.junit.Assert.assertTrue
/**
* @author Dave Syer
*/
@RunWith(SpringRunner)
@SpringBootTest(classes = Config, properties = ['spring.metrics.export.delayMillis:500',
'spring.metrics.export.enabled:true',
'initializr.metrics.prefix:test.prefix', 'initializr.metrics.key:key.test'])
class MetricsExportTests {
@Rule
public RedisRunning running = new RedisRunning()
@Autowired
ProjectGenerationMetricsListener listener
@Autowired
@Qualifier("writer")
MetricWriter writer
RedisMetricRepository repository
@Before
void init() {
repository = (RedisMetricRepository) writer
repository.findAll().each {
repository.reset(it.name)
}
assertTrue("Metrics not empty", repository.findAll().size() == 0)
}
@Test
void exportAndCheckMetricsExist() {
listener.onGeneratedProject(new ProjectGeneratedEvent(new ProjectRequest()))
Thread.sleep(1000L)
assertTrue("No metrics exported", repository.findAll().size() > 0)
}
@EnableAutoConfiguration
@EnableConfigurationProperties(InitializrProperties)
static class Config {
@Bean
InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties) {
def metadata = InitializrMetadataBuilder.fromInitializrProperties(properties).build()
new SimpleInitializrMetadataProvider(metadata)
}
}
}

View File

@@ -1,267 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.metric
import io.spring.initializr.actuate.test.MetricsAssert
import io.spring.initializr.actuate.test.TestCounterService
import io.spring.initializr.generator.ProjectFailedEvent
import io.spring.initializr.generator.ProjectGeneratedEvent
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import org.junit.Before
import org.junit.Test
/**
* @author Stephane Nicoll
*/
class ProjectGenerationMetricsListenerTests {
private InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('core', 'web', 'security', 'spring-data').build()
private ProjectGenerationMetricsListener listener
private MetricsAssert metricsAssert
@Before
void setup() {
def counterService = new TestCounterService()
listener = new ProjectGenerationMetricsListener(counterService)
metricsAssert = new MetricsAssert(counterService)
}
@Test
void projectGenerationCount() {
def request = initialize()
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests')
}
@Test
void projectGenerationCountWithFailure() {
def request = initialize()
request.resolve(metadata)
fireProjectFailedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests')
metricsAssert.hasValue(1, 'initializr.failures')
}
@Test
void dependencies() {
def request = initialize()
request.style << 'security' << 'spring-data'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.dependency.security',
'initializr.dependency.spring-data')
}
@Test
void noDependencies() {
def request = initialize()
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasNoValue('initializr.dependency.')
}
@Test
void resolvedWebDependency() {
def request = initialize()
request.style << 'spring-data'
request.packaging = 'war'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.dependency.web',
'initializr.dependency.spring-data')
}
@Test
void aliasedDependencyUseStandardId() {
def dependency = new Dependency()
dependency.id = 'foo'
dependency.aliases << 'foo-old'
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('core', dependency).build()
def request = new ProjectRequest()
request.initialize(metadata)
request.style << 'foo-old'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.dependency.foo') // standard id is used
}
@Test
void defaultType() {
def request = initialize()
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.type.maven-project')
}
@Test
void explicitType() {
def request = initialize()
request.type = 'gradle-build'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.type.gradle-build')
}
@Test
void defaultPackaging() {
def request = initialize()
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.packaging.jar')
}
@Test
void explicitPackaging() {
def request = initialize()
request.packaging = 'war'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.packaging.war')
}
@Test
void defaultJavaVersion() {
def request = initialize()
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.java_version.1_8')
}
@Test
void explicitJavaVersion() {
def request = initialize()
request.javaVersion = '1.7'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.java_version.1_7')
}
@Test
void defaultLanguage() {
def request = initialize()
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.language.java')
}
@Test
void explicitGroovyLanguage() {
def request = initialize()
request.language = 'groovy'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.language.groovy')
}
@Test
void explicitKotlinLanguage() {
def request = initialize()
request.language = 'kotlin'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.language.kotlin')
}
@Test
void defaultBootVersion() {
def request = initialize()
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.boot_version.1_2_3_RELEASE')
}
@Test
void explicitBootVersion() {
def request = initialize()
request.bootVersion = '1.0.2.RELEASE'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.boot_version.1_0_2_RELEASE')
}
@Test
void userAgentAvailable() {
def request = initialize()
request.parameters['user-agent'] = 'HTTPie/0.9.2'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.client_id.httpie')
}
@Test
void collectAllMetrics() {
def request = initialize()
request.style << 'web' << 'security'
request.type = 'gradle-project'
request.packaging = 'jar'
request.javaVersion = '1.6'
request.language = 'groovy'
request.bootVersion = '1.0.2.RELEASE'
request.parameters['user-agent'] = 'SpringBootCli/1.3.0.RELEASE'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests',
'initializr.dependency.web', 'initializr.dependency.security',
'initializr.type.gradle-project', 'initializr.packaging.jar',
'initializr.java_version.1_6', 'initializr.language.groovy',
'initializr.boot_version.1_0_2_RELEASE',
'initializr.client_id.spring').metricsCount(9)
}
@Test
void incrementMetrics() {
def request = initialize()
request.style << 'security' << 'spring-data'
request.resolve(metadata)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests',
'initializr.dependency.security', 'initializr.dependency.spring-data')
def anotherRequest = initialize()
anotherRequest.style << 'web' << 'spring-data'
anotherRequest.resolve(metadata)
fireProjectGeneratedEvent(anotherRequest)
metricsAssert.hasValue(2, 'initializr.dependency.spring-data',
'initializr.dependency.spring-data')
metricsAssert.hasValue(1, 'initializr.dependency.web',
'initializr.dependency.security')
}
private fireProjectGeneratedEvent(ProjectRequest projectRequest) {
listener.onGeneratedProject(new ProjectGeneratedEvent(projectRequest))
}
private fireProjectFailedEvent(ProjectRequest projectRequest) {
listener.onFailedProject(new ProjectFailedEvent(projectRequest, null))
}
private ProjectRequest initialize() {
def request = new ProjectRequest()
request.initialize(metadata)
request
}
}

View File

@@ -1,198 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
import groovy.json.JsonSlurper
import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests
import org.junit.Before
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Import
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.RequestEntity
import org.springframework.test.context.ActiveProfiles
import org.springframework.util.Base64Utils
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.HttpClientErrorException
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.assertTrue
import static org.junit.Assert.fail
/**
* Integration tests for stats processing.
*
* @author Stephane Nicoll
*/
@Import(StatsMockController)
@ActiveProfiles(['test-default', 'test-custom-stats'])
class MainControllerStatsIntegrationTests
extends AbstractFullStackInitializrIntegrationTests {
@Autowired
private StatsMockController statsMockController
@Autowired
private StatsProperties statsProperties
private final JsonSlurper slurper = new JsonSlurper()
@Before
public void setup() {
this.statsMockController.stats.clear()
// Make sure our mock is going to be invoked with the stats
this.statsProperties.elastic.uri = "http://localhost:$port/elastic"
}
@Test
void simpleProject() {
downloadArchive('/starter.zip?groupId=com.foo&artifactId=bar&dependencies=web')
assertEquals 'No stat got generated', 1, statsMockController.stats.size()
def content = statsMockController.stats[0]
def json = slurper.parseText(content.json)
assertEquals 'com.foo', json.groupId
assertEquals 'bar', json.artifactId
assertEquals 1, json.dependencies.size()
assertEquals 'web', json.dependencies[0]
}
@Test
void authorizationHeaderIsSet() {
downloadArchive('/starter.zip')
assertEquals 'No stat got generated', 1, statsMockController.stats.size()
def content = statsMockController.stats[0]
def authorization = content.authorization
assertNotNull 'Authorization header must be set', authorization
assertTrue 'Wrong value for authorization header', authorization.startsWith('Basic ')
def token = authorization.substring('Basic '.length(), authorization.size())
def data = new String(Base64Utils.decodeFromString(token)).split(':')
assertEquals "Wrong user from $token", 'test-user', data[0]
assertEquals "Wrong password $token", 'test-password', data[1]
}
@Test
void requestIpNotSetByDefault() {
downloadArchive('/starter.zip?groupId=com.foo&artifactId=bar&dependencies=web')
assertEquals 'No stat got generated', 1, statsMockController.stats.size()
def content = statsMockController.stats[0]
def json = slurper.parseText(content.json)
assertFalse 'requestIp property should not be set', json.containsKey('requestIp')
}
@Test
void requestIpIsSetWhenHeaderIsPresent() {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl('/starter.zip')))
.header('X-FORWARDED-FOR', '10.0.0.123').build()
restTemplate.exchange(request, String)
assertEquals 'No stat got generated', 1, statsMockController.stats.size()
def content = statsMockController.stats[0]
def json = slurper.parseText(content.json)
assertEquals 'Wrong requestIp', '10.0.0.123', json.requestIp
}
@Test
void requestIpv4IsNotSetWhenHeaderHasGarbage() {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl('/starter.zip')))
.header('x-forwarded-for', 'foo-bar').build()
restTemplate.exchange(request, String)
assertEquals 'No stat got generated', 1, statsMockController.stats.size()
def content = statsMockController.stats[0]
def json = slurper.parseText(content.json)
assertFalse 'requestIpv4 property should not be set if value is not a valid IPv4',
json.containsKey('requestIpv4')
}
@Test
void requestCountryIsNotSetWhenHeaderIsSetToXX() {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl('/starter.zip')))
.header('cf-ipcountry', 'XX').build()
restTemplate.exchange(request, String)
assertEquals 'No stat got generated', 1, statsMockController.stats.size()
def content = statsMockController.stats[0]
def json = slurper.parseText(content.json)
assertFalse 'requestCountry property should not be set if value is set to xx',
json.containsKey('requestCountry')
}
@Test
void invalidProjectSillHasStats() {
try {
downloadArchive('/starter.zip?type=invalid-type')
fail("Should have failed to generate project with invalid type")
} catch (HttpClientErrorException ex) {
assertEquals HttpStatus.BAD_REQUEST, ex.statusCode
}
assertEquals 'No stat got generated', 1, statsMockController.stats.size()
def content = statsMockController.stats[0]
def json = slurper.parseText(content.json)
assertEquals 'com.example', json.groupId
assertEquals 'demo', json.artifactId
assertEquals true, json.invalid
assertEquals true, json.invalidType
assertNotNull json.errorMessage
assertTrue json.errorMessage.contains('invalid-type')
}
@Test
void errorPublishingStatsDoesNotBubbleUp() {
this.statsProperties.elastic.uri = "http://localhost:$port/elastic-error"
downloadArchive('/starter.zip')
assertEquals 'No stat should be available', 0, statsMockController.stats.size()
}
@RestController
static class StatsMockController {
private final List<Content> stats = []
@RequestMapping(path = '/elastic/test/my-entity', method = RequestMethod.POST)
void handleProjectRequestDocument(RequestEntity<String> input) {
def authorization = input.headers.getFirst(HttpHeaders.AUTHORIZATION)
def content = new Content(authorization: authorization, json: input.body)
this.stats << content
}
@RequestMapping(path = '/elastic-error/test/my-entity', method = RequestMethod.POST)
void handleExpectedError() {
throw new IllegalStateException('Expected exception')
}
static class Content {
String authorization
String json
}
}
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
import io.spring.initializr.generator.ProjectGeneratedEvent
import io.spring.initializr.generator.ProjectRequest
import org.junit.Before
import org.junit.Test
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.retry.policy.SimpleRetryPolicy
import org.springframework.retry.support.RetryTemplate
import org.springframework.test.web.client.MockRestServiceServer
import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
/**
* @author Stephane Nicoll
*/
class ProjectGenerationStatPublisherTests extends AbstractInitializrStatTests {
private StatsProperties properties
private RetryTemplate retryTemplate
private ProjectGenerationStatPublisher statPublisher
private MockRestServiceServer mockServer
@Before
public void setUp() {
this.properties = createProperties()
ProjectRequestDocumentFactory documentFactory =
new ProjectRequestDocumentFactory(createProvider(metadata))
this.retryTemplate = new RetryTemplate()
this.statPublisher = new ProjectGenerationStatPublisher(documentFactory, properties, retryTemplate)
mockServer = MockRestServiceServer.createServer(this.statPublisher.restTemplate);
}
@Test
public void publishSimpleDocument() {
ProjectRequest request = createProjectRequest()
request.groupId = 'com.example.foo'
request.artifactId = 'my-project'
mockServer.expect(requestTo('http://example.com/elastic/initializr/request'))
.andExpect(method(HttpMethod.POST))
.andExpect(jsonPath('$.groupId').value('com.example.foo'))
.andExpect(jsonPath('$.artifactId').value('my-project'))
.andRespond(withStatus(HttpStatus.CREATED)
.body(mockResponse(UUID.randomUUID().toString(), true))
.contentType(MediaType.APPLICATION_JSON)
)
this.statPublisher.handleEvent(new ProjectGeneratedEvent(request))
mockServer.verify()
}
@Test
public void recoverFromError() {
ProjectRequest request = createProjectRequest()
mockServer.expect(requestTo('http://example.com/elastic/initializr/request'))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR))
mockServer.expect(requestTo('http://example.com/elastic/initializr/request'))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR))
mockServer.expect(requestTo('http://example.com/elastic/initializr/request'))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.CREATED)
.body(mockResponse(UUID.randomUUID().toString(), true))
.contentType(MediaType.APPLICATION_JSON))
this.statPublisher.handleEvent(new ProjectGeneratedEvent(request))
mockServer.verify()
}
@Test
public void fatalErrorOnlyLogs() {
ProjectRequest request = createProjectRequest()
this.retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)))
mockServer.expect(requestTo('http://example.com/elastic/initializr/request'))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR))
mockServer.expect(requestTo('http://example.com/elastic/initializr/request'))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR))
this.statPublisher.handleEvent(new ProjectGeneratedEvent(request))
mockServer.verify()
}
private static String mockResponse(String id, boolean created) {
'{"_index":"initializr","_type":"request","_id":"' + id + '","_version":1,"_shards"' +
':{"total":1,"successful":1,"failed":0},"created":' + created + '}'
}
private static StatsProperties createProperties() {
def properties = new StatsProperties()
properties.elastic.uri = 'http://example.com/elastic'
properties.elastic.username = 'foo'
properties.elastic.password = 'bar'
properties
}
}

View File

@@ -1,219 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
import io.spring.initializr.generator.ProjectFailedEvent
import io.spring.initializr.generator.ProjectGeneratedEvent
import io.spring.initializr.generator.ProjectRequest
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertNull
import static org.junit.Assert.assertTrue
/**
*
* @author Stephane Nicoll
*/
class ProjectRequestDocumentFactoryTests extends AbstractInitializrStatTests {
private final ProjectRequestDocumentFactory factory =
new ProjectRequestDocumentFactory(createProvider(metadata))
@Test
void createDocumentForSimpleProject() {
ProjectRequest request = createProjectRequest()
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals event.timestamp, document.generationTimestamp
assertEquals null, document.requestIp
assertEquals 'com.example', document.groupId
assertEquals 'demo', document.artifactId
assertEquals 'com.example', document.packageName
assertEquals '1.2.3.RELEASE', document.bootVersion
assertEquals '1.8', document.javaVersion
assertEquals 'java', document.language
assertEquals 'jar', document.packaging
assertEquals 'maven-project', document.type
assertEquals 0, document.dependencies.size()
assertValid document
}
@Test
void createDocumentWithRequestIp() {
ProjectRequest request = createProjectRequest()
request.parameters['x-forwarded-for'] = '10.0.0.123'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals '10.0.0.123', document.requestIp
assertEquals '10.0.0.123', document.requestIpv4
assertNull document.requestCountry
}
@Test
void createDocumentWithRequestIpv6() {
ProjectRequest request = createProjectRequest()
request.parameters['x-forwarded-for'] = '2001:db8:a0b:12f0::1'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals '2001:db8:a0b:12f0::1', document.requestIp
assertNull document.requestIpv4
assertNull document.requestCountry
}
@Test
void createDocumentWithCloudFlareHeaders() {
ProjectRequest request = createProjectRequest()
request.parameters['cf-connecting-ip'] = '10.0.0.123'
request.parameters['cf-ipcountry'] = 'BE'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals '10.0.0.123', document.requestIp
assertEquals '10.0.0.123', document.requestIpv4
assertEquals 'BE', document.requestCountry
}
@Test
void createDocumentWithCloudFlareIpv6() {
ProjectRequest request = createProjectRequest()
request.parameters['cf-connecting-ip'] = '2001:db8:a0b:12f0::1'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals '2001:db8:a0b:12f0::1', document.requestIp
assertNull document.requestIpv4
assertNull document.requestCountry
}
@Test
void createDocumentWithCloudFlareHeadersAndOtherHeaders() {
ProjectRequest request = createProjectRequest()
request.parameters['cf-connecting-ip'] = '10.0.0.123'
request.parameters['x-forwarded-for'] = '192.168.1.101'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals '10.0.0.123', document.requestIp
assertEquals '10.0.0.123', document.requestIpv4
assertNull document.requestCountry
}
@Test
void createDocumentWithCloudFlareCountrySetToXX() {
ProjectRequest request = createProjectRequest()
request.parameters['cf-connecting-ip'] = 'Xx' // case insensitive
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertNull document.requestCountry
}
@Test
void createDocumentWithUserAgent() {
ProjectRequest request = createProjectRequest()
request.parameters['user-agent'] = 'HTTPie/0.8.0'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals 'httpie', document.clientId
assertEquals '0.8.0', document.clientVersion
}
@Test
void createDocumentWithUserAgentNoVersion() {
ProjectRequest request = createProjectRequest()
request.parameters['user-agent'] = 'IntelliJ IDEA'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals 'intellijidea', document.clientId
assertEquals null, document.clientVersion
}
@Test
void createDocumentInvalidJavaVersion() {
ProjectRequest request = createProjectRequest()
request.javaVersion = '1.2'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals '1.2', document.javaVersion
assertTrue document.invalid
assertTrue document.invalidJavaVersion
}
@Test
void createDocumentInvalidLanguage() {
ProjectRequest request = createProjectRequest()
request.language = 'c++'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals 'c++', document.language
assertTrue document.invalid
assertTrue document.invalidLanguage
}
@Test
void createDocumentInvalidPackaging() {
ProjectRequest request = createProjectRequest()
request.packaging = 'ear'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals 'ear', document.packaging
assertTrue document.invalid
assertTrue document.invalidPackaging
}
@Test
void createDocumentInvalidType() {
ProjectRequest request = createProjectRequest()
request.type = 'ant-project'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals 'ant-project', document.type
assertTrue document.invalid
assertTrue document.invalidType
}
@Test
void createDocumentInvalidDependency() {
ProjectRequest request = createProjectRequest()
request.dependencies << 'web' << 'invalid' << 'data-jpa' << 'invalid-2'
def event = new ProjectGeneratedEvent(request)
def document = factory.createDocument(event)
assertEquals 'web', document.dependencies[0]
assertEquals 'data-jpa', document.dependencies[1]
assertEquals 2, document.dependencies.size()
assertTrue document.invalid
assertEquals 'invalid', document.invalidDependencies[0]
assertEquals 'invalid-2', document.invalidDependencies[1]
assertEquals 2, document.invalidDependencies.size()
}
@Test
void createDocumentWithProjectFailedEvent() {
ProjectRequest request = createProjectRequest()
def event = new ProjectFailedEvent(request, new IllegalStateException('my test message'))
def document = factory.createDocument(event)
assertTrue document.invalid
assertEquals 'my test message', document.errorMessage
}
private static void assertValid(ProjectRequestDocument document) {
assertFalse document.invalid
assertFalse document.invalidJavaVersion
assertFalse document.invalidLanguage
assertFalse document.invalidPackaging
assertEquals 0, document.invalidDependencies.size()
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.fail
/**
* Metrics assertion based on {@link TestCounterService}.
*
* @author Stephane Nicoll
*/
class MetricsAssert {
private final TestCounterService counterService
MetricsAssert(TestCounterService counterService) {
this.counterService = counterService
}
MetricsAssert hasValue(long value, String... metrics) {
metrics.each {
def actual = counterService.values[it]
if (actual == null) {
fail("Metric '$it' not found, got '${counterService.values.keySet()}")
}
assertEquals "Wrong value for metric $it", value, actual
}
this
}
MetricsAssert hasNoValue(String... metrics) {
metrics.each {
assertEquals "Metric '$it' should not be registered", null, counterService.values[it]
}
this
}
MetricsAssert metricsCount(int count) {
assertEquals "Wrong number of metrics, got '${counterService.values.keySet()}",
count, counterService.values.size()
this
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.Test;
import org.springframework.test.context.ActiveProfiles;
import groovy.json.JsonSlurper;
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests;
/**
* Tests for actuator specific features.
*
* @author Stephane Nicoll
*/
@ActiveProfiles("test-default")
public class ActuatorIntegrationTests
extends AbstractInitializrControllerIntegrationTests {
private final JsonSlurper slurper = new JsonSlurper();
@Test
public void infoHasExternalProperties() {
String body = getRestTemplate().getForObject(createUrl("/info"), String.class);
assertTrue("Wrong body:\n" + body, body.contains("\"spring-boot\""));
assertTrue("Wrong body:\n" + body,
body.contains("\"version\":\"1.1.4.RELEASE\""));
}
@Test
public void metricsAvailableByDefault() {
downloadZip("/starter.zip?packaging=jar&javaVersion=1.8&style=web&style=jpa");
Map<String, Integer> result = metricsEndpoint();
Integer requests = result.get("counter.initializr.requests");
Integer packaging = result.get("counter.initializr.packaging.jar");
Integer javaVersion = result.get("counter.initializr.java_version.1_8");
Integer webDependency = result.get("counter.initializr.dependency.web");
Integer jpaDependency = result.get("counter.initializr.dependency.jpa");
// No jpa dep this time
downloadZip("/starter.zip?packaging=jar&javaVersion=1.8&style=web");
Map<String, Integer> updatedResult = metricsEndpoint();
assertEquals("Number of request should have increased", requests + 1,
updatedResult.get("counter.initializr.requests").intValue());
assertEquals("jar packaging metric should have increased", packaging + 1,
updatedResult.get("counter.initializr.packaging.jar").intValue());
assertEquals("java version metric should have increased", javaVersion + 1,
updatedResult.get("counter.initializr.java_version.1_8").intValue());
assertEquals("web dependency metric should have increased", webDependency + 1,
updatedResult.get("counter.initializr.dependency.web").intValue());
assertEquals("jpa dependency metric should not have increased", jpaDependency,
updatedResult.get("counter.initializr.dependency.jpa"));
}
private Map<String, Integer> metricsEndpoint() {
return parseJson(getRestTemplate().getForObject(createUrl("/metrics"), String.class));
}
@SuppressWarnings("unchecked")
private Map<String, Integer> parseJson(String content) {
return (Map<String, Integer>) slurper.parseText(content);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.info;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.actuate.info.Info;
import io.spring.initializr.metadata.BillOfMaterials;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
/**
* Tests for {@link BomRangesInfoContributor}
*
* @author Stephane Nicoll
*/
public class BomRangesInfoContributorTests {
@Test
public void noBom() {
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).doesNotContainKeys("bom-ranges");
}
@Test
public void noMapping() {
BillOfMaterials bom = BillOfMaterials.create("com.example", "bom", "1.0.0");
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addBom("foo", bom).build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).doesNotContainKeys("bom-ranges");
}
@Test
public void withMappings() {
BillOfMaterials bom = BillOfMaterials.create("com.example", "bom", "1.0.0");
bom.getMappings().add(
BillOfMaterials.Mapping.create("[1.3.0.RELEASE,1.3.8.RELEASE]", "1.1.0"));
bom.getMappings().add(
BillOfMaterials.Mapping.create("1.3.8.BUILD-SNAPSHOT", "1.1.1-SNAPSHOT"));
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addBom("foo", bom).build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).containsKeys("bom-ranges");
@SuppressWarnings("unchecked")
Map<String, Object> ranges = (Map<String, Object>) info.getDetails()
.get("bom-ranges");
assertThat(ranges).containsOnlyKeys("foo");
@SuppressWarnings("unchecked")
Map<String, Object> foo = (Map<String, Object>) ranges.get("foo");
assertThat(foo).containsExactly(
entry("1.1.0", "Spring Boot >=1.3.0.RELEASE and <=1.3.8.RELEASE"),
entry("1.1.1-SNAPSHOT", "Spring Boot >=1.3.8.BUILD-SNAPSHOT"));
}
private static Info getInfo(InitializrMetadata metadata) {
Info.Builder builder = new Info.Builder();
new BomRangesInfoContributor(new SimpleInitializrMetadataProvider(metadata))
.contribute(builder);
return builder.build();
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.metric;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringRunner;
import io.spring.initializr.actuate.metric.MetricsExportTests.Config;
import io.spring.initializr.actuate.test.RedisRunning;
import io.spring.initializr.generator.ProjectGeneratedEvent;
import io.spring.initializr.generator.ProjectRequest;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataBuilder;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.metadata.InitializrProperties;
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider;
/**
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Config.class, properties = {
"spring.metrics.export.delayMillis:500", "spring.metrics.export.enabled:true",
"initializr.metrics.prefix:test.prefix", "initializr.metrics.key:key.test" })
public class MetricsExportTests {
@Rule
public RedisRunning running = new RedisRunning();
@Autowired
ProjectGenerationMetricsListener listener;
@Autowired
@Qualifier("writer")
MetricWriter writer;
RedisMetricRepository repository;
@Before
public void init() {
repository = (RedisMetricRepository) writer;
repository.findAll().forEach(it -> {
repository.reset(it.getName());
});
assertTrue("Metrics not empty", repository.count() == 0);
}
@Test
public void exportAndCheckMetricsExist() throws Exception {
listener.onGeneratedProject(new ProjectGeneratedEvent(new ProjectRequest()));
Thread.sleep(1000L);
assertTrue("No metrics exported", repository.count() > 0);
}
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties(InitializrProperties.class)
static class Config {
@Bean
InitializrMetadataProvider initializrMetadataProvider(
InitializrProperties properties) {
InitializrMetadata metadata = InitializrMetadataBuilder
.fromInitializrProperties(properties).build();
return new SimpleInitializrMetadataProvider(metadata);
}
}
}

View File

@@ -0,0 +1,270 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.metric;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import io.spring.initializr.actuate.test.MetricsAssert;
import io.spring.initializr.actuate.test.TestCounterService;
import io.spring.initializr.generator.ProjectFailedEvent;
import io.spring.initializr.generator.ProjectGeneratedEvent;
import io.spring.initializr.generator.ProjectRequest;
import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
/**
* @author Stephane Nicoll
*/
public class ProjectGenerationMetricsListenerTests {
private InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup("core", "web", "security", "spring-data").build();
private ProjectGenerationMetricsListener listener;
private MetricsAssert metricsAssert;
@Before
public void setup() {
TestCounterService counterService = new TestCounterService();
listener = new ProjectGenerationMetricsListener(counterService);
metricsAssert = new MetricsAssert(counterService);
}
@Test
public void projectGenerationCount() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.requests");
}
@Test
public void projectGenerationCountWithFailure() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectFailedEvent(request);
metricsAssert.hasValue(1, "initializr.requests");
metricsAssert.hasValue(1, "initializr.failures");
}
@Test
public void dependencies() {
ProjectRequest request = initialize();
request.getStyle().addAll(Arrays.asList("security", "spring-data"));
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.dependency.security",
"initializr.dependency.spring-data");
}
@Test
public void noDependencies() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasNoValue("initializr.dependency.");
}
@Test
public void resolvedWebDependency() {
ProjectRequest request = initialize();
request.getStyle().add("spring-data");
request.setPackaging("war");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.dependency.web",
"initializr.dependency.spring-data");
}
@Test
public void aliasedDependencyUseStandardId() {
Dependency dependency = new Dependency();
dependency.setId("foo");
dependency.getAliases().add("foo-old");
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup("core", dependency).build();
ProjectRequest request = new ProjectRequest();
request.initialize(metadata);
request.getStyle().add("foo-old");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.dependency.foo"); // standard id is used
}
@Test
public void defaultType() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.type.maven-project");
}
@Test
public void explicitType() {
ProjectRequest request = initialize();
request.setType("gradle-build");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.type.gradle-build");
}
@Test
public void defaultPackaging() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.packaging.jar");
}
@Test
public void explicitPackaging() {
ProjectRequest request = initialize();
request.setPackaging("war");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.packaging.war");
}
@Test
public void defaultJavaVersion() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.java_version.1_8");
}
@Test
public void explicitJavaVersion() {
ProjectRequest request = initialize();
request.setJavaVersion("1.7");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.java_version.1_7");
}
@Test
public void defaultLanguage() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.language.java");
}
@Test
public void explicitGroovyLanguage() {
ProjectRequest request = initialize();
request.setLanguage("groovy");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.language.groovy");
}
@Test
public void explicitKotlinLanguage() {
ProjectRequest request = initialize();
request.setLanguage("kotlin");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.language.kotlin");
}
@Test
public void defaultBootVersion() {
ProjectRequest request = initialize();
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.boot_version.1_2_3_RELEASE");
}
@Test
public void explicitBootVersion() {
ProjectRequest request = initialize();
request.setBootVersion("1.0.2.RELEASE");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.boot_version.1_0_2_RELEASE");
}
@Test
public void userAgentAvailable() {
ProjectRequest request = initialize();
request.getParameters().put("user-agent", "HTTPie/0.9.2");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.client_id.httpie");
}
@Test
public void collectAllMetrics() {
ProjectRequest request = initialize();
request.getStyle().addAll(Arrays.asList("web", "security"));
request.setType("gradle-project");
request.setPackaging("jar");
request.setJavaVersion("1.6");
request.setLanguage("groovy");
request.setBootVersion("1.0.2.RELEASE");
request.getParameters().put("user-agent", "SpringBootCli/1.3.0.RELEASE");
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.requests",
"initializr.dependency.web", "initializr.dependency.security",
"initializr.type.gradle-project", "initializr.packaging.jar",
"initializr.java_version.1_6", "initializr.language.groovy",
"initializr.boot_version.1_0_2_RELEASE",
"initializr.client_id.spring").metricsCount(9);
}
@Test
public void incrementMetrics() {
ProjectRequest request = initialize();
request.getStyle().addAll(Arrays.asList("security","spring-data"));
request.resolve(metadata);
fireProjectGeneratedEvent(request);
metricsAssert.hasValue(1, "initializr.requests",
"initializr.dependency.security", "initializr.dependency.spring-data");
ProjectRequest anotherRequest = initialize();
anotherRequest.getStyle().addAll(Arrays.asList("web", "spring-data"));
anotherRequest.resolve(metadata);
fireProjectGeneratedEvent(anotherRequest);
metricsAssert.hasValue(2, "initializr.dependency.spring-data",
"initializr.dependency.spring-data");
metricsAssert.hasValue(1, "initializr.dependency.web",
"initializr.dependency.security");
}
private void fireProjectGeneratedEvent(ProjectRequest projectRequest) {
listener.onGeneratedProject(new ProjectGeneratedEvent(projectRequest));
}
private void fireProjectFailedEvent(ProjectRequest projectRequest) {
listener.onFailedProject(new ProjectFailedEvent(projectRequest, null));
}
private ProjectRequest initialize() {
ProjectRequest request = new ProjectRequest();
request.initialize(metadata);
return request;
}
}

View File

@@ -14,34 +14,35 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
package io.spring.initializr.actuate.stat;
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import io.spring.initializr.generator.ProjectRequest;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
/**
* @author Stephane Nicoll
*/
abstract class AbstractInitializrStatTests {
def metadata = InitializrMetadataTestBuilder
InitializrMetadata metadata = InitializrMetadataTestBuilder
.withDefaults()
.addDependencyGroup('core', 'security', 'validation', 'aop')
.addDependencyGroup('web', 'web', 'data-rest', 'jersey')
.addDependencyGroup('data', 'data-jpa', 'jdbc')
.addDependencyGroup('database', 'h2', 'mysql')
.build()
.addDependencyGroup("core", "security", "validation", "aop")
.addDependencyGroup("web", "web", "data-rest", "jersey")
.addDependencyGroup("data", "data-jpa", "jdbc")
.addDependencyGroup("database", "h2", "mysql")
.build();
protected InitializrMetadataProvider createProvider(def metadata) {
new SimpleInitializrMetadataProvider(metadata)
protected InitializrMetadataProvider createProvider(InitializrMetadata metadata) {
return new SimpleInitializrMetadataProvider(metadata);
}
protected ProjectRequest createProjectRequest() {
ProjectRequest request = new ProjectRequest()
request.initialize(metadata)
request
ProjectRequest request = new ProjectRequest();
request.initialize(metadata);
return request;
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import groovy.json.JsonSlurper;
import io.spring.initializr.actuate.stat.MainControllerStatsIntegrationTests.StatsMockController;
import io.spring.initializr.actuate.stat.MainControllerStatsIntegrationTests.StatsMockController.Content;
import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests;
/**
* Integration tests for stats processing.
*
* @author Stephane Nicoll
*/
@Import(StatsMockController.class)
@ActiveProfiles({ "test-default", "test-custom-stats" })
public class MainControllerStatsIntegrationTests
extends AbstractFullStackInitializrIntegrationTests {
@Autowired
private StatsMockController statsMockController;
@Autowired
private StatsProperties statsProperties;
private final JsonSlurper slurper = new JsonSlurper();
@Before
public void setup() {
this.statsMockController.stats.clear();
// Make sure our mock is going to be invoked with the stats
this.statsProperties.getElastic().setUri( "http://localhost:" + port + "/elastic");
}
@Test
public void simpleProject() {
downloadArchive("/starter.zip?groupId=com.foo&artifactId=bar&dependencies=web");
assertEquals("No stat got generated", 1, statsMockController.stats.size());
Content content = statsMockController.stats.get(0);
@SuppressWarnings("unchecked")
Map<String,Object> json = (Map<String, Object>) slurper.parseText(content.json);
assertEquals("com.foo", json.get("groupId"));
assertEquals("bar", json.get("artifactId"));
@SuppressWarnings("unchecked")
List<String> list = (List<String>) json.get("dependencies");
assertEquals(1, list.size());
assertEquals("web", list.get(0));
}
@Test
public void authorizationHeaderIsSet() {
downloadArchive("/starter.zip");
assertEquals("No stat got generated", 1, statsMockController.stats.size());
Content content = statsMockController.stats.get(0);
String authorization = content.authorization;
assertNotNull("Authorization header must be set", authorization);
assertTrue("Wrong value for authorization header", authorization.startsWith("Basic "));
String token = authorization.substring("Basic ".length(), authorization.length());
String[] data = new String(Base64Utils.decodeFromString(token)).split(":");
assertEquals("Wrong user from $token", "test-user", data[0]);
assertEquals("Wrong password $token", "test-password", data[1]);
}
@Test
public void requestIpNotSetByDefault() {
downloadArchive("/starter.zip?groupId=com.foo&artifactId=bar&dependencies=web");
assertEquals("No stat got generated", 1, statsMockController.stats.size());
Content content = statsMockController.stats.get(0);
@SuppressWarnings("unchecked")
Map<String,Object> json = (Map<String, Object>) slurper.parseText(content.json);
assertFalse("requestIp property should not be set", json.containsKey("requestIp"));
}
@Test
public void requestIpIsSetWhenHeaderIsPresent() throws Exception {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl("/starter.zip")))
.header("X-FORWARDED-FOR", "10.0.0.123").build();
getRestTemplate().exchange(request, String.class);
assertEquals("No stat got generated", 1, statsMockController.stats.size());
Content content = statsMockController.stats.get(0);
@SuppressWarnings("unchecked")
Map<String,Object> json = (Map<String, Object>) slurper.parseText(content.json);
assertEquals("Wrong requestIp", "10.0.0.123", json.get("requestIp"));
}
@Test
public void requestIpv4IsNotSetWhenHeaderHasGarbage() throws Exception {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl("/starter.zip")))
.header("x-forwarded-for", "foo-bar").build();
getRestTemplate().exchange(request, String.class);
assertEquals("No stat got generated", 1, statsMockController.stats.size());
Content content = statsMockController.stats.get(0);
@SuppressWarnings("unchecked")
Map<String,Object> json = (Map<String, Object>) slurper.parseText(content.json);
assertFalse("requestIpv4 property should not be set if value is not a valid IPv4",
json.containsKey("requestIpv4"));
}
@Test
public void requestCountryIsNotSetWhenHeaderIsSetToXX() throws Exception {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl("/starter.zip")))
.header("cf-ipcountry", "XX").build();
getRestTemplate().exchange(request, String.class);
assertEquals("No stat got generated", 1, statsMockController.stats.size());
Content content = statsMockController.stats.get(0);
@SuppressWarnings("unchecked")
Map<String,Object> json = (Map<String, Object>) slurper.parseText(content.json);
assertFalse("requestCountry property should not be set if value is set to xx",
json.containsKey("requestCountry"));
}
@Test
public void invalidProjectSillHasStats() {
try {
downloadArchive("/starter.zip?type=invalid-type");
fail("Should have failed to generate project with invalid type");
} catch (HttpClientErrorException ex) {
assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode());
}
assertEquals("No stat got generated", 1, statsMockController.stats.size());
Content content = statsMockController.stats.get(0);
@SuppressWarnings("unchecked")
Map<String,Object> json = (Map<String, Object>) slurper.parseText(content.json);
assertEquals("com.example", json.get("groupId"));
assertEquals("demo", json.get("artifactId"));
assertEquals(true, json.get("invalid"));
assertEquals(true, json.get("invalidType"));
assertNotNull(json.get("errorMessage"));
assertTrue(((String) json.get("errorMessage")).contains("invalid-type"));
}
@Test
public void errorPublishingStatsDoesNotBubbleUp() {
this.statsProperties.getElastic()
.setUri("http://localhost:" + port + "/elastic-error");
downloadArchive("/starter.zip");
assertEquals("No stat should be available", 0, statsMockController.stats.size());
}
@RestController
static class StatsMockController {
private final List<Content> stats = new ArrayList<>();
@RequestMapping(path = "/elastic/test/my-entity", method = RequestMethod.POST)
void handleProjectRequestDocument(RequestEntity<String> input) {
String authorization = input.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
Content content = new Content(authorization, input.getBody());
this.stats.add(content);
}
@RequestMapping(path = "/elastic-error/test/my-entity", method = RequestMethod.POST)
void handleExpectedError() {
throw new IllegalStateException("Expected exception");
}
static class Content {
public Content(String authorization, String body) {
this.authorization = authorization;
json = body;
}
String authorization;
String json;
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the \"License\");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an \"AS IS\" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
import java.util.Collections;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.test.web.client.MockRestServiceServer;
import io.spring.initializr.actuate.stat.StatsProperties.Elastic;
import io.spring.initializr.generator.ProjectGeneratedEvent;
import io.spring.initializr.generator.ProjectRequest;
/**
* @author Stephane Nicoll
*/
public class ProjectGenerationStatPublisherTests extends AbstractInitializrStatTests {
private StatsProperties properties;
private RetryTemplate retryTemplate;
private ProjectGenerationStatPublisher statPublisher;
private MockRestServiceServer mockServer;
@Before
public void setUp() {
this.properties = createProperties();
ProjectRequestDocumentFactory documentFactory =
new ProjectRequestDocumentFactory(createProvider(metadata));
this.retryTemplate = new RetryTemplate();
this.statPublisher = new ProjectGenerationStatPublisher(documentFactory, properties, retryTemplate);
mockServer = MockRestServiceServer.createServer(this.statPublisher.getRestTemplate());
}
@Test
public void publishSimpleDocument() {
ProjectRequest request = createProjectRequest();
request.setGroupId("com.example.foo");
request.setArtifactId("my-project");
mockServer.expect(requestTo("http://example.com/elastic/initializr/request"))
.andExpect(method(HttpMethod.POST))
.andExpect(jsonPath("$.groupId").value("com.example.foo"))
.andExpect(jsonPath("$.artifactId").value("my-project"))
.andRespond(withStatus(HttpStatus.CREATED)
.body(mockResponse(UUID.randomUUID().toString(), true))
.contentType(MediaType.APPLICATION_JSON)
);
this.statPublisher.handleEvent(new ProjectGeneratedEvent(request));
mockServer.verify();
}
@Test
public void recoverFromError() {
ProjectRequest request = createProjectRequest();
mockServer.expect(requestTo("http://example.com/elastic/initializr/request"))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
mockServer.expect(requestTo("http://example.com/elastic/initializr/request"))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
mockServer.expect(requestTo("http://example.com/elastic/initializr/request"))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.CREATED)
.body(mockResponse(UUID.randomUUID().toString(), true))
.contentType(MediaType.APPLICATION_JSON));
this.statPublisher.handleEvent(new ProjectGeneratedEvent(request));
mockServer.verify();
}
@Test
public void fatalErrorOnlyLogs() {
ProjectRequest request = createProjectRequest();
this.retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
mockServer.expect(requestTo("http://example.com/elastic/initializr/request"))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
mockServer.expect(requestTo("http://example.com/elastic/initializr/request"))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
this.statPublisher.handleEvent(new ProjectGeneratedEvent(request));
mockServer.verify();
}
private static String mockResponse(String id, boolean created) {
return "{\"_index\":\"initializr\",\"_type\":\"request\",\"_id\":\"" + id + "\",\"_version\":1,\"_shards\"" +
":{\"total\":1,\"successful\":1,\"failed\":0},\"created\":" + created + "}";
}
private static StatsProperties createProperties() {
StatsProperties properties = new StatsProperties();
Elastic elastic = properties.getElastic();
elastic.setUri("http://example.com/elastic");
elastic.setUsername("foo");
elastic.setPassword("bar");
return properties;
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.stat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import org.junit.Test;
import io.spring.initializr.generator.ProjectFailedEvent;
import io.spring.initializr.generator.ProjectGeneratedEvent;
import io.spring.initializr.generator.ProjectRequest;
/**
*
* @author Stephane Nicoll
*/
public class ProjectRequestDocumentFactoryTests extends AbstractInitializrStatTests {
private final ProjectRequestDocumentFactory factory =
new ProjectRequestDocumentFactory(createProvider(metadata));
@Test
public void createDocumentForSimpleProject() {
ProjectRequest request = createProjectRequest();
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals(event.getTimestamp(), document.getGenerationTimestamp());
assertEquals(null, document.getRequestIp());
assertEquals("com.example", document.getGroupId());
assertEquals("demo", document.getArtifactId());
assertEquals("com.example", document.getPackageName());
assertEquals("1.2.3.RELEASE", document.getBootVersion());
assertEquals("1.8", document.getJavaVersion());
assertEquals("java", document.getLanguage());
assertEquals("jar", document.getPackaging());
assertEquals("maven-project", document.getType());
assertEquals(0, document.getDependencies().size());
assertValid(document);
}
@Test
public void createDocumentWithRequestIp() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("x-forwarded-for","10.0.0.123");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("10.0.0.123", document.getRequestIp());
assertEquals("10.0.0.123", document.getRequestIpv4());
assertNull(document.getRequestCountry());
}
@Test
public void createDocumentWithRequestIpv6() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("x-forwarded-for", "2001:db8:a0b:12f0::1");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("2001:db8:a0b:12f0::1", document.getRequestIp());
assertNull(document.getRequestIpv4());
assertNull(document.getRequestCountry());
}
@Test
public void createDocumentWithCloudFlareHeaders() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("cf-connecting-ip", "10.0.0.123");
request.getParameters().put("cf-ipcountry","BE");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("10.0.0.123", document.getRequestIp());
assertEquals("10.0.0.123", document.getRequestIpv4());
assertEquals("BE", document.getRequestCountry());
}
@Test
public void createDocumentWithCloudFlareIpv6() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("cf-connecting-ip", "2001:db8:a0b:12f0::1");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("2001:db8:a0b:12f0::1", document.getRequestIp());
assertNull(document.getRequestIpv4());
assertNull(document.getRequestCountry());
}
@Test
public void createDocumentWithCloudFlareHeadersAndOtherHeaders() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("cf-connecting-ip", "10.0.0.123");
request.getParameters().put("x-forwarded-for", "192.168.1.101");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("10.0.0.123", document.getRequestIp());
assertEquals("10.0.0.123", document.getRequestIpv4());
assertNull(document.getRequestCountry());
}
@Test
public void createDocumentWithCloudFlareCountrySetToXX() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("cf-connecting-ip", "Xx"); // case insensitive
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertNull(document.getRequestCountry());
}
@Test
public void createDocumentWithUserAgent() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("user-agent","HTTPie/0.8.0");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("httpie", document.getClientId());
assertEquals("0.8.0", document.getClientVersion());
}
@Test
public void createDocumentWithUserAgentNoVersion() {
ProjectRequest request = createProjectRequest();
request.getParameters().put("user-agent","IntelliJ IDEA");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("intellijidea", document.getClientId());
assertEquals(null, document.getClientVersion());
}
@Test
public void createDocumentInvalidJavaVersion() {
ProjectRequest request = createProjectRequest();
request.setJavaVersion("1.2");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("1.2", document.getJavaVersion());
assertTrue(document.isInvalid());
assertTrue(document.isInvalidJavaVersion());
}
@Test
public void createDocumentInvalidLanguage() {
ProjectRequest request = createProjectRequest();
request.setLanguage("c++");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("c++", document.getLanguage());
assertTrue(document.isInvalid());
assertTrue(document.isInvalidLanguage());
}
@Test
public void createDocumentInvalidPackaging() {
ProjectRequest request = createProjectRequest();
request.setPackaging("ear");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("ear", document.getPackaging());
assertTrue(document.isInvalid());
assertTrue(document.isInvalidPackaging());
}
@Test
public void createDocumentInvalidType() {
ProjectRequest request = createProjectRequest();
request.setType("ant-project");
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("ant-project", document.getType());
assertTrue(document.isInvalid());
assertTrue(document.isInvalidType());
}
@Test
public void createDocumentInvalidDependency() {
ProjectRequest request = createProjectRequest();
request.setDependencies(Arrays.asList("web", "invalid", "data-jpa", "invalid-2"));
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request);
ProjectRequestDocument document = factory.createDocument(event);
assertEquals("web", document.getDependencies().get(0));
assertEquals("data-jpa", document.getDependencies().get(1));
assertEquals(2, document.getDependencies().size());
assertTrue(document.isInvalid());
assertEquals("invalid", document.getInvalidDependencies().get(0));
assertEquals("invalid-2", document.getInvalidDependencies().get(1));
assertEquals(2, document.getInvalidDependencies().size());
}
@Test
public void createDocumentWithProjectFailedEvent() {
ProjectRequest request = createProjectRequest();
ProjectFailedEvent event = new ProjectFailedEvent(request, new IllegalStateException("my test message"));
ProjectRequestDocument document = factory.createDocument(event);
assertTrue(document.isInvalid());
assertEquals("my test message", document.getErrorMessage());
}
private static void assertValid(ProjectRequestDocument document) {
assertFalse(document.isInvalid());
assertFalse(document.isInvalidJavaVersion());
assertFalse(document.isInvalidLanguage());
assertFalse(document.isInvalidPackaging());
assertEquals(0, document.getInvalidDependencies().size());
}
}

View File

@@ -14,34 +14,34 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.stat
package io.spring.initializr.actuate.stat;
import io.spring.initializr.actuate.stat.StatsProperties
import org.junit.Test
import io.spring.initializr.actuate.stat.StatsProperties;
import org.junit.Test;
import static org.junit.Assert.assertThat
import static org.hamcrest.CoreMatchers.is
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
/**
* @author Stephane Nicoll
*/
class StatsPropertiesTests {
public class StatsPropertiesTests {
private final StatsProperties properties = new StatsProperties()
private final StatsProperties properties = new StatsProperties();
@Test
void cleanTrailingSlash() {
properties.elastic.uri = 'http://example.com/'
assertThat(properties.elastic.uri, is('http://example.com'))
public void cleanTrailingSlash() {
properties.getElastic().setUri("http://example.com/");
assertThat(properties.getElastic().getUri(), is("http://example.com"));
}
@Test
void provideEntityUrl() {
properties.elastic.uri = 'http://example.com/'
properties.elastic.indexName = 'my-index'
properties.elastic.entityName = 'foo'
assertThat(properties.elastic.entityUrl.toString(),
is('http://example.com/my-index/foo'))
public void provideEntityUrl() {
properties.getElastic().setUri("http://example.com/");
properties.getElastic().setIndexName("my-index");
properties.getElastic().setEntityName("foo");
assertThat(properties.getElastic().getEntityUrl().toString(),
is("http://example.com/my-index/foo"));
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.actuate.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Arrays;
/**
* Metrics assertion based on {@link TestCounterService}.
*
* @author Stephane Nicoll
*/
public class MetricsAssert {
private final TestCounterService counterService;
public MetricsAssert(TestCounterService counterService) {
this.counterService = counterService;
}
public MetricsAssert hasValue(long value, String... metrics) {
Arrays.asList(metrics).forEach(it -> {
Long actual = counterService.values.get(it);
if (actual == null) {
fail("Metric '" + it + "' not found, got '"
+ counterService.values.keySet() + "'");
}
assertEquals("Wrong value for metric " + it, value, actual.longValue());
});
return this;
}
public MetricsAssert hasNoValue(String... metrics) {
Arrays.asList(metrics).forEach(it -> {
assertEquals("Metric '" + it + "' should not be registered", null,
counterService.values.get(it));
});
return this;
}
public MetricsAssert metricsCount(int count) {
assertEquals(
"Wrong number of metrics, got '" + counterService.values.keySet() + "'",
count, counterService.values.size());
return this;
}
}

View File

@@ -14,36 +14,35 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.test
package io.spring.initializr.actuate.test;
import org.junit.Assume
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.junit.runners.model.Statement
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory
import org.junit.Assume;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
/**
* A {@link org.junit.rules.TestRule} that validates Redis is available.
*
* @author Dave Syer
*/
class RedisRunning extends TestWatcher {
public class RedisRunning extends TestWatcher {
JedisConnectionFactory connectionFactory;
@Override
Statement apply(Statement base, Description description) {
public Statement apply(Statement base, Description description) {
if (connectionFactory == null) {
connectionFactory = new JedisConnectionFactory()
connectionFactory.afterPropertiesSet()
connectionFactory = new JedisConnectionFactory();
connectionFactory.afterPropertiesSet();
}
try {
connectionFactory.connection
connectionFactory.getConnection();
} catch (Exception e) {
Assume.assumeNoException('Cannot connect to Redis (so skipping tests)', e)
Assume.assumeNoException("Cannot connect to Redis (so skipping tests)", e);
}
super.apply(base, description)
return super.apply(base, description);
}
}

View File

@@ -14,36 +14,39 @@
* limitations under the License.
*/
package io.spring.initializr.actuate.test
package io.spring.initializr.actuate.test;
import org.springframework.boot.actuate.metrics.CounterService
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.metrics.CounterService;
/**
* A test {@link CounterService} that keeps track of the metric values.
*
* @author Stephane Nicoll
*/
class TestCounterService implements CounterService {
public class TestCounterService implements CounterService {
final Map<String, Long> values = [:]
final Map<String, Long> values = new HashMap<>();
@Override
void increment(String metricName) {
def value = values[metricName]
def valueToSet = value ? ++value : 1
values[metricName] = valueToSet
public void increment(String metricName) {
Long value = values.get(metricName);
Long valueToSet = value!=null ? ++value : 1;
values.put(metricName, valueToSet);
}
@Override
void decrement(String metricName) {
def value = values[metricName]
def valueToSet = value ? --value : -1
values[metricName] = valueToSet
public void decrement(String metricName) {
Long value = values.get(metricName);
Long valueToSet = value!=null ? +--value : -1;
values.put(metricName, valueToSet);
}
@Override
void reset(String metricName) {
values[metricName] = 0
public void reset(String metricName) {
values.put(metricName, 0L);
}
}

View File

@@ -39,7 +39,7 @@ class ProjectRequest extends BasicProjectRequest {
/**
* The id of the starter to use if no dependency is defined.
*/
static final DEFAULT_STARTER = 'root_starter'
public static final DEFAULT_STARTER = 'root_starter'
/**
* Additional parameters that can be used to further identify the request.

View File

@@ -29,12 +29,12 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Config.class, webEnvironment = RANDOM_PORT)
@SpringBootTest(webEnvironment = RANDOM_PORT)
abstract class AbstractFullStackInitializrIntegrationTests
extends AbstractInitializrIntegrationTests {
@LocalServerPort
int port
protected int port
String host = "localhost"