/*
* Copyright 2014 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.xd.analytics.metrics.redis;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.retry.RetryOperations;
import org.springframework.util.Assert;
import org.springframework.xd.analytics.metrics.core.Metric;
import org.springframework.xd.analytics.metrics.core.MetricRepository;
/**
* Common base functionality for Redis implementations.
*
* Only handles single values (not lists, maps etc).
*
* @author Luke Taylor
*/
abstract class AbstractRedisMetricRepository<M extends Metric, V> implements MetricRepository<M> {
private final String metricPrefix;
private final ValueOperations<String, V> valueOperations;
public ValueOperations<String, V> getValueOperations() {
return valueOperations;
}
public RedisOperations<String, V> getRedisOperations() {
return redisOperations;
}
private final RedisOperations<String, V> redisOperations;
@SuppressWarnings("unchecked")
AbstractRedisMetricRepository(RedisConnectionFactory connectionFactory, String metricPrefix, Class<V> valueClass) {
this(connectionFactory, metricPrefix, valueClass, null);
}
@SuppressWarnings("unchecked")
AbstractRedisMetricRepository(RedisConnectionFactory connectionFactory, String metricPrefix, Class<V> valueClass,
RetryOperations retryOperations) {
Assert.notNull(connectionFactory);
Assert.hasText(metricPrefix, "metric prefix cannot be empty");
this.metricPrefix = metricPrefix;
this.redisOperations = RedisUtils.createRedisRetryTemplate(connectionFactory, valueClass, retryOperations);
this.valueOperations = redisOperations.opsForValue();
}
@Override
public void deleteAll() {
Set<String> keys = redisOperations.keys(metricPrefix + "*");
if (keys.size() > 0) {
redisOperations.delete(keys);
}
}
/**
* Template method to create a single instance of the metric.
*
* @param name the metric name
* @param value the initial value.
* @return the metric instance
*/
abstract M create(String name, V value);
/**
* @return The default value for a metric (usually zero).
*/
abstract V defaultValue();
/**
* @return the value carried by the given metric
*/
abstract V value(M metric);
/**
* Provides the key for a named metric. By default this appends 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;
}
@Override
public <S extends M> S save(S metric) {
String metricKey = getMetricKey(metric.getName());
valueOperations.set(metricKey, value(metric));
return metric;
}
@Override
public <S extends M> Iterable<S> save(Iterable<S> metrics) {
List<S> results = new ArrayList<S>();
for (S m : metrics) {
results.add(save(m));
}
return results;
}
@Override
public void delete(String name) {
Assert.notNull(name, "The name of the metric must not be null");
this.redisOperations.delete(getMetricKey(name));
}
@Override
public void delete(M metric) {
Assert.notNull(metric, "The metric must not be null");
this.redisOperations.delete(getMetricKey(metric.getName()));
}
@Override
public void delete(Iterable<? extends M> metrics) {
for (M metric : metrics) {
delete(metric);
}
}
@Override
public M findOne(String name) {
Assert.notNull(name, "The name of the metric must not be null");
String gaugeKey = getMetricKey(name);
if (redisOperations.hasKey(gaugeKey)) {
V value = this.valueOperations.get(gaugeKey);
return create(name, value);
}
else {
return null;
}
}
@Override
public boolean exists(String s) {
return findOne(s) != null;
}
@Override
public List<M> findAll() {
List<M> gauges = new ArrayList<M>();
// TODO asking for keys is not recommended. See http://redis.io/commands/keys
// Need to keep track of created instances explicitly.
Set<String> keys = this.redisOperations.keys(this.metricPrefix + "*");
for (String key : keys) {
if (!key.matches(metricPrefix + ".+?_\\d{4}\\.\\d{2}\\.\\d{2}-\\d{2}:\\d{2}")) {
V value = this.valueOperations.get(key);
String name = key.substring(metricPrefix.length());
M m = create(name, value);
gauges.add(m);
}
}
return gauges;
}
@Override
public Iterable<M> findAll(Iterable<String> keys) {
List<M> results = new ArrayList<M>();
for (String k : keys) {
M value = findOne(k);
if (value != null) {
results.add(value);
}
}
return results;
}
@Override
public long count() {
return findAll().size();
}
}