/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.module.metrics.redis;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.springframework.cloud.stream.module.metrics.FieldValueCounter;
import org.springframework.cloud.stream.module.metrics.FieldValueCounterRepository;
import org.springframework.cloud.stream.module.retry.StringRedisRetryTemplate;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.retry.RetryOperations;
import org.springframework.util.Assert;
public class RedisFieldValueCounterRepository implements FieldValueCounterRepository {
private final String metricPrefix;
private final StringRedisRetryTemplate redisTemplate;
public RedisFieldValueCounterRepository(RedisConnectionFactory connectionFactory, RetryOperations retryOperations) {
this(connectionFactory, "fieldvaluecounters.", retryOperations);
}
public RedisFieldValueCounterRepository(RedisConnectionFactory connectionFactory, String metricPrefix,
RetryOperations retryOperations) {
Assert.notNull(connectionFactory);
Assert.hasText(metricPrefix, "metric prefix cannot be empty");
this.metricPrefix = metricPrefix;
redisTemplate = new StringRedisRetryTemplate(connectionFactory, retryOperations);
// avoids proxy
redisTemplate.setExposeConnection(true);
redisTemplate.afterPropertiesSet();
}
@Override
public FieldValueCounter findOne(String name) {
Assert.notNull(name, "The name of the FieldValueCounter must not be null");
String metricKey = getMetricKey(name);
if (redisTemplate.hasKey(metricKey)) {
Map<String, Double> values = getZSetData(metricKey);
FieldValueCounter c = new FieldValueCounter(name, values);
return c;
}
else {
return null;
}
}
@Override
public Collection<String> list() {
Set<String> keys = redisTemplate.keys(getMetricKey("*"));
Set<String> names = new HashSet<>(keys.size());
for (String key : keys) {
names.add(getCounterName(key));
}
return names;
}
@Override
public void increment(String counterName, String fieldName, double score) {
redisTemplate.boundZSetOps(getMetricKey(counterName)).incrementScore(fieldName, score);
}
@Override
public void decrement(String counterName, String fieldName, double score) {
redisTemplate.boundZSetOps(getMetricKey(counterName)).incrementScore(fieldName, -score);
}
@Override
public void reset(String counterName) {
redisTemplate.delete(getMetricKey(counterName));
}
/**
* Provides the key for a named metric. By default this prepends the name to the metricPrefix value.
*
* @param metricName the name of the metric
* @return the redis key under which the metric is stored
*/
protected String getMetricKey(String metricName) {
return metricPrefix + metricName;
}
/**
* Provides the name of a counter stored under a given key. This operation is the reverse of {@link #getMetricKey(String)}.
*/
private String getCounterName(String redisKey) {
return redisKey.substring(metricPrefix.length());
}
protected Map<String, Double> getZSetData(String counterKey) {
Set<ZSetOperations.TypedTuple<String>> rangeWithScore = this.redisTemplate
.boundZSetOps(counterKey).rangeWithScores(0, -1);
Map<String, Double> values = new HashMap<String, Double>(
rangeWithScore.size());
for (Iterator<ZSetOperations.TypedTuple<String>> iterator = rangeWithScore.iterator(); iterator
.hasNext();) {
ZSetOperations.TypedTuple<String> typedTuple = iterator.next();
values.put(typedTuple.getValue(), typedTuple.getScore());
}
return values;
}
}