/*
* Copyright 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 org.springframework.cloud.stream.metrics;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.boot.actuate.metrics.export.MetricExportProperties;
import org.springframework.boot.actuate.metrics.export.TriggerProperties;
import org.springframework.boot.bind.RelaxedNames;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.stream.metrics.config.BinderMetricsAutoConfiguration;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
/**
* @author Vinicius Carvalho
*/
@ConfigurationProperties(prefix = "spring.cloud.stream.metrics")
public class ApplicationMetricsProperties
implements ApplicationListener<ContextRefreshedEvent> {
private String prefix = "";
@Value("${spring.application.name:${vcap.application.name:${spring.config.name:application}}}")
private String key;
private String metricName;
private String[] properties;
private final MetricExportProperties metricExportProperties;
public TriggerProperties getTrigger() {
return metricExportProperties.findTrigger(BinderMetricsAutoConfiguration.APPLICATION_METRICS_EXPORTER_TRIGGER_NAME);
}
public ApplicationMetricsProperties(MetricExportProperties metricExportProperties) {
Assert.notNull(metricExportProperties, "'metricsExportProperties' cannot be null");
this.metricExportProperties = metricExportProperties;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
if (!prefix.endsWith(".")) {
prefix += ".";
}
this.prefix = prefix;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String[] getProperties() {
return properties;
}
public void setProperties(String[] properties) {
this.properties = properties;
}
/**
* List of properties that are going to be appended to each message. This gets
* populate by onApplicationEvent, once the context refreshes to avoid overhead of
* doing per message basis.
*/
private Map<String, Object> exportProperties = new HashMap<>();
public Map<String, Object> getExportProperties() {
return exportProperties;
}
public String getMetricName() {
if (this.metricName == null) {
this.metricName = resolveMetricName();
}
return metricName;
}
private String resolveMetricName() {
return this.prefix + this.key;
}
/**
* Iterates over all property sources from this application context and copies the
* ones listed in {@link ApplicationMetricsProperties} includes.
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
this.exportProperties.clear();
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) event.getSource();
ConfigurableEnvironment environment = ctx.getEnvironment();
BeanExpressionResolver beanExpressionResolver = ctx.getBeanFactory().getBeanExpressionResolver();
BeanExpressionContext expressionContext = new BeanExpressionContext(ctx.getBeanFactory(), null);
if (!ObjectUtils.isEmpty(this.properties)) {
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> e = (EnumerablePropertySource<?>) source;
for (String propertyName : e.getPropertyNames()) {
RelaxedNames relaxedNames = new RelaxedNames(propertyName);
String canonicalFormat = RelaxedPropertiesUtils.findCanonicalFormat(relaxedNames);
// omit this property if already populated from a
// higher priority source
if (!this.exportProperties.containsKey(canonicalFormat)) {
relaxedLoop: for (String relaxedPropertyName : relaxedNames) {
if (isMatch(relaxedPropertyName, this.properties, null)) {
Object value = source.getProperty(propertyName);
String stringValue = ObjectUtils.nullSafeToString(value);
Object exportedValue = null;
if (value != null) {
exportedValue = stringValue.startsWith("#{")
? beanExpressionResolver.evaluate(
environment.resolvePlaceholders(stringValue), expressionContext)
: environment.resolvePlaceholders(stringValue);
}
this.exportProperties.put(
canonicalFormat,
exportedValue);
break relaxedLoop;
}
}
}
}
}
}
}
}
private boolean isMatch(String name, String[] includes, String[] excludes) {
if (ObjectUtils.isEmpty(includes)
|| PatternMatchUtils.simpleMatch(includes, name)) {
return !PatternMatchUtils.simpleMatch(excludes, name);
}
return false;
}
}