mirror of
https://gitee.com/dcren/initializr.git
synced 2025-12-02 11:24:04 +08:00
Modularize project structure
This commit splits the feature of Spring Initializr in several modules: * `initializr-generator` is a standalone library that is responsible for generating projects based on a `File` directory. It has a minimal set of dependencies and is not web-related * `initializr-web` provides the web integration (project generation, meta-data, etc) * `initializr-actuator` is an optional module that can be added to support project-generation-specific statistics Closes gh-214
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.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
|
||||
|
||||
/**
|
||||
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
* Auto-configuration} to handle the metrics of an initializr instance.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.0
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureAfter([RedisAutoConfiguration, MetricExportAutoConfiguration])
|
||||
class InitializrMetricsConfiguration {
|
||||
|
||||
@Bean
|
||||
ProjectGenerationMetricsListener metricsListener(CounterService counterService) {
|
||||
new ProjectGenerationMetricsListener(counterService)
|
||||
}
|
||||
|
||||
@ConditionalOnBean(RedisConnectionFactory)
|
||||
@ConditionalOnProperty(value = 'spring.metrics.export.enabled')
|
||||
@EnableScheduling
|
||||
@EnableConfigurationProperties(MetricsProperties)
|
||||
@Configuration
|
||||
public static class MetricsExportConfiguration {
|
||||
|
||||
@Autowired
|
||||
RedisConnectionFactory connectionFactory
|
||||
|
||||
@Autowired
|
||||
MetricsProperties metrics
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context
|
||||
|
||||
@Bean
|
||||
@ExportMetricWriter
|
||||
MetricWriter writer() {
|
||||
new RedisMetricRepository(connectionFactory,
|
||||
metrics.prefix + metrics.getId(context.getId()) + '.'
|
||||
+ ObjectUtils.getIdentityHexString(context) + '.',
|
||||
metrics.key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.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 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
|
||||
|
||||
/**
|
||||
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
* Auto-configuration} to publish statistics of each generated project.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(StatsProperties)
|
||||
@ConditionalOnProperty('initializr.stats.elastic.uri')
|
||||
class InitializrStatsAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private StatsProperties statsProperties
|
||||
|
||||
@Bean
|
||||
ProjectGenerationStatPublisher projectRequestStatHandler(InitializrMetadataProvider provider) {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.autoconfigure
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
/**
|
||||
* Metrics-related configuration.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.0
|
||||
*/
|
||||
@ConfigurationProperties('initializr.metrics')
|
||||
class MetricsProperties {
|
||||
|
||||
/**
|
||||
* Prefix for redis keys holding metrics in data store.
|
||||
*/
|
||||
String prefix = 'spring.metrics.collector.'
|
||||
|
||||
/**
|
||||
* Redis key holding index to metrics keys in data store.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@Value('${spring.application.name:${vcap.application.name:application}}')
|
||||
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
|
||||
|
||||
String getPrefix() {
|
||||
if (prefix.endsWith('.')) {
|
||||
return prefix
|
||||
}
|
||||
prefix + '.'
|
||||
}
|
||||
|
||||
String getId(String defaultValue) {
|
||||
if (id) return id
|
||||
defaultValue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.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
|
||||
* @since 1.0
|
||||
*/
|
||||
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('.', '_')
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 java.nio.charset.StandardCharsets
|
||||
|
||||
import org.springframework.http.HttpRequest
|
||||
import org.springframework.http.client.ClientHttpRequestExecution
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor
|
||||
import org.springframework.http.client.ClientHttpResponse
|
||||
import org.springframework.http.client.InterceptingClientHttpRequestFactory
|
||||
import org.springframework.util.Base64Utils
|
||||
import org.springframework.web.client.RestTemplate
|
||||
|
||||
/**
|
||||
* A simple {@link RestTemplate} extension that automatically provides the
|
||||
* {@code Authorization} header if credentials are provided.
|
||||
* <p>
|
||||
* Largely inspired from Spring Boot's {@code TestRestTemplate}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.0
|
||||
*/
|
||||
class BasicAuthRestTemplate extends RestTemplate {
|
||||
|
||||
/**
|
||||
* Create a new instance. {@code username} and {@code password} can be
|
||||
* {@code null} if no authentication is necessary.
|
||||
*/
|
||||
BasicAuthRestTemplate(String username, String password) {
|
||||
addAuthentication(username, password)
|
||||
}
|
||||
|
||||
private void addAuthentication(String username, String password) {
|
||||
if (!username) {
|
||||
return;
|
||||
}
|
||||
List<ClientHttpRequestInterceptor> interceptors = Collections
|
||||
.<ClientHttpRequestInterceptor> singletonList(
|
||||
new BasicAuthorizationInterceptor(username, password))
|
||||
setRequestFactory(new InterceptingClientHttpRequestFactory(getRequestFactory(),
|
||||
interceptors))
|
||||
}
|
||||
|
||||
private static class BasicAuthorizationInterceptor
|
||||
implements ClientHttpRequestInterceptor {
|
||||
|
||||
private final String username
|
||||
|
||||
private final String password
|
||||
|
||||
BasicAuthorizationInterceptor(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = (password == null ? "" : password)
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
|
||||
ClientHttpRequestExecution execution) throws IOException {
|
||||
String token = Base64Utils.encodeToString(
|
||||
(this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8))
|
||||
request.getHeaders().add("Authorization", "Basic " + token)
|
||||
return execution.execute(request, body)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 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.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
|
||||
* @since 1.0
|
||||
*/
|
||||
@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 BasicAuthRestTemplate(
|
||||
statsProperties.elastic.username, statsProperties.elastic.password)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.transform.ToString
|
||||
|
||||
/**
|
||||
* Define the statistics of a project generation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.0
|
||||
*/
|
||||
@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 = []
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 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
|
||||
* @since 1.0
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.util.StringUtils
|
||||
|
||||
/**
|
||||
* Statistics-related properties.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.0
|
||||
*/
|
||||
@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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "initializr.stats",
|
||||
"type": "io.spring.initializr.actuate.stat.StatsProperties",
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties"
|
||||
},
|
||||
{
|
||||
"name": "initializr.stats.elastic",
|
||||
"type": "io.spring.initializr.actuate.stat.StatsProperties$Elastic",
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties",
|
||||
"sourceMethod": "getElastic()"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "initializr.stats.elastic.uri",
|
||||
"type": "java.lang.String",
|
||||
"description": "Elastic service uri.",
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties$Elastic"
|
||||
},
|
||||
{
|
||||
"name": "initializr.stats.elastic.username",
|
||||
"type": "java.lang.String",
|
||||
"description": "Elastic service username.",
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties$Elastic"
|
||||
},
|
||||
{
|
||||
"name": "initializr.stats.elastic.password",
|
||||
"type": "java.lang.String",
|
||||
"description": "Elastic service password.",
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties$Elastic"
|
||||
},
|
||||
{
|
||||
"name": "initializr.stats.elastic.index-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Name of the index.",
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties$Elastic"
|
||||
},
|
||||
{
|
||||
"name": "initializr.stats.elastic.entity-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Name of the entity to use to publish stats.",
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties$Elastic"
|
||||
},
|
||||
{
|
||||
"name": "initializr.stats.elastic.max-attempts",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "Number of attempts before giving up.",
|
||||
"defaultValue": 3,
|
||||
"sourceType": "io.spring.initializr.actuate.stat.StatsProperties$Elastic"
|
||||
}
|
||||
],
|
||||
"hints": []
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
io.spring.initializr.actuate.autoconfigure.InitializrStatsAutoConfiguration,\
|
||||
io.spring.initializr.actuate.autoconfigure.InitializrMetricsConfiguration
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.IntegrationTest
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
|
||||
import static org.junit.Assert.assertTrue
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner)
|
||||
@SpringApplicationConfiguration(classes = Config)
|
||||
@IntegrationTest(['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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.ProjectRequest
|
||||
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
|
||||
.withDefaults()
|
||||
.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 ProjectRequest createProjectRequest() {
|
||||
ProjectRequest request = new ProjectRequest()
|
||||
request.initialize(metadata)
|
||||
request
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.AbstractInitializrControllerIntegrationTests
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration
|
||||
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
|
||||
*/
|
||||
@SpringApplicationConfiguration(StatsMockController.class)
|
||||
@ActiveProfiles(['test-default', 'test-custom-stats'])
|
||||
class MainControllerStatsIntegrationTests extends AbstractInitializrControllerIntegrationTests {
|
||||
|
||||
@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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.actuate.stat.StatsProperties
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertThat
|
||||
import static org.hamcrest.CoreMatchers.is
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class StatsPropertiesTests {
|
||||
|
||||
private final StatsProperties properties = new StatsProperties()
|
||||
|
||||
@Test
|
||||
void cleanTrailingSlash() {
|
||||
properties.elastic.uri = 'http://example.com/'
|
||||
assertThat(properties.elastic.uri, 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'))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 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
|
||||
* @since 1.0
|
||||
*/
|
||||
class RedisRunning extends TestWatcher {
|
||||
|
||||
JedisConnectionFactory connectionFactory;
|
||||
|
||||
@Override
|
||||
Statement apply(Statement base, Description description) {
|
||||
if (connectionFactory == null) {
|
||||
connectionFactory = new JedisConnectionFactory()
|
||||
connectionFactory.afterPropertiesSet()
|
||||
}
|
||||
try {
|
||||
connectionFactory.connection
|
||||
} catch (Exception e) {
|
||||
Assume.assumeNoException('Cannot connect to Redis (so skipping tests)', e)
|
||||
}
|
||||
super.apply(base, description)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 org.springframework.boot.actuate.metrics.CounterService
|
||||
|
||||
/**
|
||||
* A test {@link CounterService} that keeps track of the metric values.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class TestCounterService implements CounterService {
|
||||
|
||||
final Map<String, Long> values = [:]
|
||||
|
||||
@Override
|
||||
void increment(String metricName) {
|
||||
def value = values[metricName]
|
||||
def valueToSet = value ? ++value : 1
|
||||
values[metricName] = valueToSet
|
||||
}
|
||||
|
||||
@Override
|
||||
void decrement(String metricName) {
|
||||
def value = values[metricName]
|
||||
def valueToSet = value ? --value : -1
|
||||
values[metricName] = valueToSet
|
||||
}
|
||||
|
||||
@Override
|
||||
void reset(String metricName) {
|
||||
values[metricName] = 0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
initializr:
|
||||
stats:
|
||||
elastic:
|
||||
uri: http://localhost:${server.port}/elastic
|
||||
indexName: test
|
||||
entityName: my-entity
|
||||
username: test-user
|
||||
password: test-password
|
||||
max-attempts: 1
|
||||
Reference in New Issue
Block a user