All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.redis.connection.convert.Converters Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/*
 * Copyright 2013-2018 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.data.redis.connection.convert;

import lombok.RequiredArgsConstructor;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.core.convert.converter.Converter;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisClusterNode.Flag;
import org.springframework.data.redis.connection.RedisClusterNode.LinkState;
import org.springframework.data.redis.connection.RedisClusterNode.RedisClusterNodeBuilder;
import org.springframework.data.redis.connection.RedisClusterNode.SlotRange;
import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.connection.RedisNode.NodeType;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * Common type converters
 *
 * @author Jennifer Hickey
 * @author Thomas Darimont
 * @author Mark Paluch
 * @author Christoph Strobl
 */
abstract public class Converters {

	private static final byte[] ONE = new byte[] { '1' };
	private static final byte[] ZERO = new byte[] { '0' };
	private static final String CLUSTER_NODES_LINE_SEPARATOR = "\n";
	private static final Converter STRING_TO_PROPS = new StringToPropertiesConverter();
	private static final Converter LONG_TO_BOOLEAN = new LongToBooleanConverter();
	private static final Converter STRING_TO_DATA_TYPE = new StringToDataTypeConverter();
	private static final Converter, Properties> MAP_TO_PROPERTIES = MapToPropertiesConverter.INSTANCE;
	private static final Converter STRING_TO_CLUSTER_NODE_CONVERTER;
	private static final Converter, Properties> STRING_LIST_TO_PROPERTIES_CONVERTER;
	private static final Map flagLookupMap;

	static {

		flagLookupMap = new LinkedHashMap<>(Flag.values().length, 1);
		for (Flag flag : Flag.values()) {
			flagLookupMap.put(flag.getRaw(), flag);
		}

		STRING_TO_CLUSTER_NODE_CONVERTER = new Converter() {

			static final int ID_INDEX = 0;
			static final int HOST_PORT_INDEX = 1;
			static final int FLAGS_INDEX = 2;
			static final int MASTER_ID_INDEX = 3;
			static final int LINK_STATE_INDEX = 7;
			static final int SLOTS_INDEX = 8;

			@Override
			public RedisClusterNode convert(String source) {

				String[] args = source.split(" ");
				String[] hostAndPort = StringUtils.split(args[HOST_PORT_INDEX], ":");

				Assert.notNull(hostAndPort, "CusterNode information does not define host and port!");

				SlotRange range = parseSlotRange(args);
				Set flags = parseFlags(args);

				String portPart = hostAndPort[1];
				if (portPart.contains("@")) {
					portPart = portPart.substring(0, portPart.indexOf('@'));
				}

				RedisClusterNodeBuilder nodeBuilder = RedisClusterNode.newRedisClusterNode()
						.listeningAt(hostAndPort[0], Integer.valueOf(portPart)) //
						.withId(args[ID_INDEX]) //
						.promotedAs(flags.contains(Flag.MASTER) ? NodeType.MASTER : NodeType.SLAVE) //
						.serving(range) //
						.withFlags(flags) //
						.linkState(parseLinkState(args));

				if (!args[MASTER_ID_INDEX].isEmpty() && !args[MASTER_ID_INDEX].startsWith("-")) {
					nodeBuilder.slaveOf(args[MASTER_ID_INDEX]);
				}

				return nodeBuilder.build();
			}

			private Set parseFlags(String[] args) {

				String raw = args[FLAGS_INDEX];

				Set flags = new LinkedHashSet<>(8, 1);
				if (StringUtils.hasText(raw)) {
					for (String flag : raw.split(",")) {
						flags.add(flagLookupMap.get(flag));
					}
				}
				return flags;
			}

			private LinkState parseLinkState(String[] args) {

				String raw = args[LINK_STATE_INDEX];

				if (StringUtils.hasText(raw)) {
					return LinkState.valueOf(raw.toUpperCase());
				}
				return LinkState.DISCONNECTED;
			}

			private SlotRange parseSlotRange(String[] args) {

				Set slots = new LinkedHashSet<>();

				for (int i = SLOTS_INDEX; i < args.length; i++) {

					String raw = args[i];

					if (raw.startsWith("[")) {
						continue;
					}

					if (raw.contains("-")) {
						String[] slotRange = StringUtils.split(raw, "-");

						if (slotRange != null) {
							int from = Integer.valueOf(slotRange[0]);
							int to = Integer.valueOf(slotRange[1]);
							for (int slot = from; slot <= to; slot++) {
								slots.add(slot);
							}
						}
					} else {
						slots.add(Integer.valueOf(raw));
					}
				}

				return new SlotRange(slots);
			}

		};

		STRING_LIST_TO_PROPERTIES_CONVERTER = input -> {

			Assert.notNull(input, "Input list must not be null!");
			Assert.isTrue(input.size() % 2 == 0, "Input list must contain an even number of entries!");

			Properties properties = new Properties();

			for (int i = 0; i < input.size(); i += 2) {

				properties.setProperty(input.get(i), input.get(i + 1));
			}

			return properties;
		};
	}

	public static Boolean stringToBoolean(String s) {
		return stringToBooleanConverter().convert(s);
	}

	public static Converter stringToBooleanConverter() {
		return (source) -> ObjectUtils.nullSafeEquals("OK", source);
	}

	public static Converter stringToProps() {
		return STRING_TO_PROPS;
	}

	public static Converter longToBoolean() {
		return LONG_TO_BOOLEAN;
	}

	public static Converter stringToDataType() {
		return STRING_TO_DATA_TYPE;
	}

	public static Properties toProperties(String source) {
		return STRING_TO_PROPS.convert(source);
	}

	public static Properties toProperties(Map source) {

		Properties properties = MAP_TO_PROPERTIES.convert(source);
		return properties != null ? properties : new Properties();
	}

	public static Boolean toBoolean(Long source) {
		return LONG_TO_BOOLEAN.convert(source);
	}

	public static DataType toDataType(String source) {
		return STRING_TO_DATA_TYPE.convert(source);
	}

	public static byte[] toBit(Boolean source) {
		return (source ? ONE : ZERO);
	}

	/**
	 * Converts the result of a single line of {@code CLUSTER NODES} into a {@link RedisClusterNode}.
	 *
	 * @param clusterNodesLine
	 * @return
	 * @since 1.7
	 */
	protected static RedisClusterNode toClusterNode(String clusterNodesLine) {
		return STRING_TO_CLUSTER_NODE_CONVERTER.convert(clusterNodesLine);
	}

	/**
	 * Converts lines from the result of {@code CLUSTER NODES} into {@link RedisClusterNode}s.
	 *
	 * @param lines
	 * @return
	 * @since 1.7
	 */
	public static Set toSetOfRedisClusterNodes(Collection lines) {

		if (CollectionUtils.isEmpty(lines)) {
			return Collections.emptySet();
		}

		Set nodes = new LinkedHashSet<>(lines.size());

		for (String line : lines) {
			nodes.add(toClusterNode(line));
		}

		return nodes;
	}

	/**
	 * Converts the result of {@code CLUSTER NODES} into {@link RedisClusterNode}s.
	 *
	 * @param clusterNodes
	 * @return
	 * @since 1.7
	 */
	public static Set toSetOfRedisClusterNodes(String clusterNodes) {

		if (StringUtils.isEmpty(clusterNodes)) {
			return Collections.emptySet();
		}

		String[] lines = clusterNodes.split(CLUSTER_NODES_LINE_SEPARATOR);
		return toSetOfRedisClusterNodes(Arrays.asList(lines));
	}

	public static List toObjects(Set tuples) {
		List tupleArgs = new ArrayList<>(tuples.size() * 2);
		for (Tuple tuple : tuples) {
			tupleArgs.add(tuple.getScore());
			tupleArgs.add(tuple.getValue());
		}
		return tupleArgs;
	}

	/**
	 * Returns the timestamp constructed from the given {@code seconds} and {@code microseconds}.
	 *
	 * @param seconds server time in seconds
	 * @param microseconds elapsed microseconds in current second
	 * @return
	 */
	public static Long toTimeMillis(String seconds, String microseconds) {
		return NumberUtils.parseNumber(seconds, Long.class) * 1000L
				+ NumberUtils.parseNumber(microseconds, Long.class) / 1000L;
	}

	/**
	 * Converts {@code seconds} to the given {@link TimeUnit}.
	 *
	 * @param seconds
	 * @param targetUnit must not be {@literal null}.
	 * @return
	 * @since 1.8
	 */
	public static long secondsToTimeUnit(long seconds, TimeUnit targetUnit) {

		Assert.notNull(targetUnit, "TimeUnit must not be null!");

		if (seconds > 0) {
			return targetUnit.convert(seconds, TimeUnit.SECONDS);
		}

		return seconds;
	}

	/**
	 * Creates a new {@link Converter} to convert from seconds to the given {@link TimeUnit}.
	 *
	 * @param timeUnit muist not be {@literal null}.
	 * @return
	 * @since 1.8
	 */
	public static Converter secondsToTimeUnit(final TimeUnit timeUnit) {

		return seconds -> secondsToTimeUnit(seconds, timeUnit);
	}

	/**
	 * Converts {@code milliseconds} to the given {@link TimeUnit}.
	 *
	 * @param milliseconds
	 * @param targetUnit must not be {@literal null}.
	 * @return
	 * @since 1.8
	 */
	public static long millisecondsToTimeUnit(long milliseconds, TimeUnit targetUnit) {

		Assert.notNull(targetUnit, "TimeUnit must not be null!");

		if (milliseconds > 0) {
			return targetUnit.convert(milliseconds, TimeUnit.MILLISECONDS);
		}

		return milliseconds;
	}

	/**
	 * Creates a new {@link Converter} to convert from milliseconds to the given {@link TimeUnit}.
	 *
	 * @param timeUnit muist not be {@literal null}.
	 * @return
	 * @since 1.8
	 */
	public static Converter millisecondsToTimeUnit(final TimeUnit timeUnit) {

		return seconds -> millisecondsToTimeUnit(seconds, timeUnit);
	}

	/**
	 * {@link Converter} capable of deserializing {@link GeoResults}.
	 *
	 * @param serializer
	 * @return
	 * @since 1.8
	 */
	public static  Converter>, GeoResults>> deserializingGeoResultsConverter(
			RedisSerializer serializer) {
		return new DeserializingGeoResultsConverter<>(serializer);
	}

	/**
	 * {@link Converter} capable of converting Double into {@link Distance} using given {@link Metric}.
	 *
	 * @param metric
	 * @return
	 * @since 1.8
	 */
	public static Converter distanceConverterForMetric(Metric metric) {
		return DistanceConverterFactory.INSTANCE.forMetric(metric);
	}

	/**
	 * Converts array outputs with key-value sequences (such as produced by {@code CONFIG GET}) from a {@link List} to
	 * {@link Properties}.
	 *
	 * @param input must not be {@literal null}.
	 * @return the mapped result.
	 * @since 2.0
	 */
	public static Properties toProperties(List input) {
		return STRING_LIST_TO_PROPERTIES_CONVERTER.convert(input);
	}

	/**
	 * Returns a converter to convert array outputs with key-value sequences (such as produced by {@code CONFIG GET}) from
	 * a {@link List} to {@link Properties}.
	 *
	 * @return the converter.
	 * @since 2.0
	 */
	public static Converter, Properties> listToPropertiesConverter() {
		return STRING_LIST_TO_PROPERTIES_CONVERTER;
	}

	/**
	 * Returns a converter to convert from {@link Map} to {@link Properties}.
	 *
	 * @return the converter.
	 * @since 2.0
	 */
	@SuppressWarnings("unchecked")
	public static  Converter, Properties> mapToPropertiesConverter() {
		return (Converter) MAP_TO_PROPERTIES;
	}

	/**
	 * Convert the given {@literal nullable seconds} to a {@link Duration} or {@literal null}.
	 *
	 * @param seconds can be {@literal null}.
	 * @return given {@literal seconds} as {@link Duration} or {@literal null}.
	 * @since 2.1
	 */
	@Nullable
	public static Duration secondsToDuration(@Nullable Long seconds) {
		return seconds != null ? Duration.ofSeconds(seconds) : null;

	}

	/**
	 * @author Christoph Strobl
	 * @since 1.8
	 */
	enum DistanceConverterFactory {

		INSTANCE;

		/**
		 * @param metric can be {@literal null}. Defaults to {@link DistanceUnit#METERS}.
		 * @return never {@literal null}.
		 */
		DistanceConverter forMetric(@Nullable Metric metric) {
			return new DistanceConverter(
					metric == null || ObjectUtils.nullSafeEquals(Metrics.NEUTRAL, metric) ? DistanceUnit.METERS : metric);
		}

		static class DistanceConverter implements Converter {

			private Metric metric;

			/**
			 * @param metric can be {@literal null}. Defaults to {@link DistanceUnit#METERS}.
			 * @return never {@literal null}.
			 */
			DistanceConverter(@Nullable Metric metric) {
				this.metric = metric == null || ObjectUtils.nullSafeEquals(Metrics.NEUTRAL, metric) ? DistanceUnit.METERS
						: metric;
			}

			/*
			 * (non-Javadoc)
			 * @see org.springframework.core.convert.converter.Converter#convert(Object)
			 */
			@Override
			public Distance convert(Double source) {
				return new Distance(source, metric);
			}
		}
	}

	/**
	 * @author Christoph Strobl
	 * @param 
	 * @since 1.8
	 */
	@RequiredArgsConstructor
	static class DeserializingGeoResultsConverter
			implements Converter>, GeoResults>> {

		final RedisSerializer serializer;

		/*
		 * (non-Javadoc)
		 * @see org.springframework.core.convert.converter.Converter#convert(Object)
		 */
		@Override
		public GeoResults> convert(GeoResults> source) {

			List>> values = new ArrayList<>(source.getContent().size());
			for (GeoResult> value : source.getContent()) {

				values.add(new GeoResult<>(
						new GeoLocation<>(serializer.deserialize(value.getContent().getName()), value.getContent().getPoint()),
						value.getDistance()));
			}

			return new GeoResults<>(values, source.getAverageDistance().getMetric());
		}
	}
}