/* * Copyright 2013-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.data.redis.connection.lettuce; import io.lettuce.core.*; import io.lettuce.core.cluster.models.partitions.Partitions; import io.lettuce.core.cluster.models.partitions.RedisClusterNode.NodeFlag; import io.lettuce.core.protocol.LettuceCharsets; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; 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.geo.Point; import org.springframework.data.redis.connection.DefaultTuple; 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.SlotRange; import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit; import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation; import org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs; import org.springframework.data.redis.connection.RedisListCommands.Position; import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisNode.NodeType; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisServer; import org.springframework.data.redis.connection.RedisStringCommands.SetOption; import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.RedisZSetCommands.Range.Boundary; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.SortParameters.Order; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.ListConverter; import org.springframework.data.redis.connection.convert.LongToBooleanConverter; import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter; import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.data.redis.util.ByteUtils; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Lettuce type converters * * @author Jennifer Hickey * @author Christoph Strobl * @author Thomas Darimont * @author Mark Paluch * @author Ninad Divadkar */ abstract public class LettuceConverters extends Converters { private static final Converter<Date, Long> DATE_TO_LONG; private static final Converter<List<byte[]>, Set<byte[]>> BYTES_LIST_TO_BYTES_SET; private static final Converter<byte[], String> BYTES_TO_STRING; private static final Converter<String, byte[]> STRING_TO_BYTES; private static final Converter<Set<byte[]>, List<byte[]>> BYTES_SET_TO_BYTES_LIST; private static final Converter<Collection<byte[]>, List<byte[]>> BYTES_COLLECTION_TO_BYTES_LIST; private static final Converter<KeyValue<byte[], byte[]>, List<byte[]>> KEY_VALUE_TO_BYTES_LIST; private static final Converter<List<ScoredValue<byte[]>>, Set<Tuple>> SCORED_VALUES_TO_TUPLE_SET; private static final Converter<List<ScoredValue<byte[]>>, List<Tuple>> SCORED_VALUES_TO_TUPLE_LIST; private static final Converter<ScoredValue<byte[]>, Tuple> SCORED_VALUE_TO_TUPLE; private static final Converter<Exception, DataAccessException> EXCEPTION_CONVERTER = new LettuceExceptionConverter(); private static final Converter<Long, Boolean> LONG_TO_BOOLEAN = new LongToBooleanConverter(); private static final Converter<List<byte[]>, Map<byte[], byte[]>> BYTES_LIST_TO_MAP; private static final Converter<List<byte[]>, List<Tuple>> BYTES_LIST_TO_TUPLE_LIST_CONVERTER; private static final Converter<String[], List<RedisClientInfo>> STRING_TO_LIST_OF_CLIENT_INFO = new StringToRedisClientInfoConverter(); private static final Converter<Partitions, List<RedisClusterNode>> PARTITIONS_TO_CLUSTER_NODES; private static Converter<io.lettuce.core.cluster.models.partitions.RedisClusterNode, RedisClusterNode> CLUSTER_NODE_TO_CLUSTER_NODE_CONVERTER; private static final Converter<List<byte[]>, Long> BYTES_LIST_TO_TIME_CONVERTER; private static final Converter<GeoCoordinates, Point> GEO_COORDINATE_TO_POINT_CONVERTER; private static final ListConverter<GeoCoordinates, Point> GEO_COORDINATE_LIST_TO_POINT_LIST_CONVERTER; private static final Converter<KeyValue<Object, Object>, Object> KEY_VALUE_UNWRAPPER; private static final ListConverter<KeyValue<Object, Object>, Object> KEY_VALUE_LIST_UNWRAPPER; private static final Converter<TransactionResult, List<Object>> TRANSACTION_RESULT_UNWRAPPER; public static final byte[] PLUS_BYTES; public static final byte[] MINUS_BYTES; public static final byte[] POSITIVE_INFINITY_BYTES; public static final byte[] NEGATIVE_INFINITY_BYTES; static { DATE_TO_LONG = new Converter<Date, Long>() { public Long convert(Date source) { return source != null ? source.getTime() : null; } }; BYTES_LIST_TO_BYTES_SET = new Converter<List<byte[]>, Set<byte[]>>() { public Set<byte[]> convert(List<byte[]> results) { return results != null ? new LinkedHashSet<>(results) : null; } }; BYTES_TO_STRING = new Converter<byte[], String>() { @Override public String convert(byte[] source) { if (source == null || Arrays.equals(source, new byte[0])) { return null; } return new String(source); } }; STRING_TO_BYTES = new Converter<String, byte[]>() { @Override public byte[] convert(String source) { if (source == null) { return null; } return source.getBytes(); } }; BYTES_SET_TO_BYTES_LIST = new Converter<Set<byte[]>, List<byte[]>>() { public List<byte[]> convert(Set<byte[]> results) { return results != null ? new ArrayList<>(results) : null; } }; BYTES_COLLECTION_TO_BYTES_LIST = new Converter<Collection<byte[]>, List<byte[]>>() { public List<byte[]> convert(Collection<byte[]> results) { if (results instanceof List) { return (List<byte[]>) results; } return results != null ? new ArrayList<>(results) : null; } }; KEY_VALUE_TO_BYTES_LIST = new Converter<KeyValue<byte[], byte[]>, List<byte[]>>() { public List<byte[]> convert(KeyValue<byte[], byte[]> source) { if (source == null) { return null; } List<byte[]> list = new ArrayList<>(2); list.add(source.getKey()); list.add(source.getValue()); return list; } }; BYTES_LIST_TO_MAP = new Converter<List<byte[]>, Map<byte[], byte[]>>() { @Override public Map<byte[], byte[]> convert(final List<byte[]> source) { if (CollectionUtils.isEmpty(source)) { return Collections.emptyMap(); } Map<byte[], byte[]> target = new LinkedHashMap<>(); Iterator<byte[]> kv = source.iterator(); while (kv.hasNext()) { target.put(kv.next(), kv.hasNext() ? kv.next() : null); } return target; } }; SCORED_VALUES_TO_TUPLE_SET = new Converter<List<ScoredValue<byte[]>>, Set<Tuple>>() { public Set<Tuple> convert(List<ScoredValue<byte[]>> source) { if (source == null) { return null; } Set<Tuple> tuples = new LinkedHashSet<>(source.size()); for (ScoredValue<byte[]> value : source) { tuples.add(LettuceConverters.toTuple(value)); } return tuples; } }; SCORED_VALUES_TO_TUPLE_LIST = new Converter<List<ScoredValue<byte[]>>, List<Tuple>>() { public List<Tuple> convert(List<ScoredValue<byte[]>> source) { if (source == null) { return null; } List<Tuple> tuples = new ArrayList<>(source.size()); for (ScoredValue<byte[]> value : source) { tuples.add(LettuceConverters.toTuple(value)); } return tuples; } }; SCORED_VALUE_TO_TUPLE = new Converter<ScoredValue<byte[]>, Tuple>() { public Tuple convert(ScoredValue<byte[]> source) { return source != null ? new DefaultTuple(source.getValue(), Double.valueOf(source.getScore())) : null; } }; BYTES_LIST_TO_TUPLE_LIST_CONVERTER = new Converter<List<byte[]>, List<Tuple>>() { @Override public List<Tuple> convert(List<byte[]> source) { if (CollectionUtils.isEmpty(source)) { return Collections.emptyList(); } List<Tuple> tuples = new ArrayList<>(); Iterator<byte[]> it = source.iterator(); while (it.hasNext()) { tuples.add( new DefaultTuple(it.next(), it.hasNext() ? Double.valueOf(LettuceConverters.toString(it.next())) : null)); } return tuples; } }; PARTITIONS_TO_CLUSTER_NODES = new Converter<Partitions, List<RedisClusterNode>>() { @Override public List<RedisClusterNode> convert(Partitions source) { if (source == null) { return Collections.emptyList(); } List<RedisClusterNode> nodes = new ArrayList<>(); for (io.lettuce.core.cluster.models.partitions.RedisClusterNode node : source.getPartitions()) { nodes.add(CLUSTER_NODE_TO_CLUSTER_NODE_CONVERTER.convert(node)); } return nodes; }; }; CLUSTER_NODE_TO_CLUSTER_NODE_CONVERTER = new Converter<io.lettuce.core.cluster.models.partitions.RedisClusterNode, RedisClusterNode>() { @Override public RedisClusterNode convert(io.lettuce.core.cluster.models.partitions.RedisClusterNode source) { Set<Flag> flags = parseFlags(source.getFlags()); return RedisClusterNode.newRedisClusterNode().listeningAt(source.getUri().getHost(), source.getUri().getPort()) .withId(source.getNodeId()).promotedAs(flags.contains(Flag.MASTER) ? NodeType.MASTER : NodeType.SLAVE) .serving(new SlotRange(source.getSlots())).withFlags(flags) .linkState(source.isConnected() ? LinkState.CONNECTED : LinkState.DISCONNECTED).slaveOf(source.getSlaveOf()) .build(); } private Set<Flag> parseFlags(Set<NodeFlag> source) { Set<Flag> flags = new LinkedHashSet<>(source != null ? source.size() : 8, 1); for (NodeFlag flag : source) { switch (flag) { case NOFLAGS: flags.add(Flag.NOFLAGS); break; case EVENTUAL_FAIL: flags.add(Flag.PFAIL); break; case FAIL: flags.add(Flag.FAIL); break; case HANDSHAKE: flags.add(Flag.HANDSHAKE); break; case MASTER: flags.add(Flag.MASTER); break; case MYSELF: flags.add(Flag.MYSELF); break; case NOADDR: flags.add(Flag.NOADDR); break; case SLAVE: flags.add(Flag.SLAVE); break; } } return flags; } }; PLUS_BYTES = toBytes("+"); MINUS_BYTES = toBytes("-"); POSITIVE_INFINITY_BYTES = toBytes("+inf"); NEGATIVE_INFINITY_BYTES = toBytes("-inf"); BYTES_LIST_TO_TIME_CONVERTER = new Converter<List<byte[]>, Long>() { @Override public Long convert(List<byte[]> source) { Assert.notEmpty(source, "Received invalid result from server. Expected 2 items in collection."); Assert.isTrue(source.size() == 2, "Received invalid nr of arguments from redis server. Expected 2 received " + source.size()); return toTimeMillis(LettuceConverters.toString(source.get(0)), LettuceConverters.toString(source.get(1))); } }; GEO_COORDINATE_TO_POINT_CONVERTER = new Converter<io.lettuce.core.GeoCoordinates, Point>() { @Override public Point convert(io.lettuce.core.GeoCoordinates geoCoordinate) { return geoCoordinate != null ? new Point(geoCoordinate.getX().doubleValue(), geoCoordinate.getY().doubleValue()) : null; } }; GEO_COORDINATE_LIST_TO_POINT_LIST_CONVERTER = new ListConverter<>(GEO_COORDINATE_TO_POINT_CONVERTER); KEY_VALUE_UNWRAPPER = new Converter<KeyValue<Object, Object>, Object>() { @Override public Object convert(KeyValue<Object, Object> source) { return source.getValueOrElse(null); } }; KEY_VALUE_LIST_UNWRAPPER = new ListConverter<>(KEY_VALUE_UNWRAPPER); TRANSACTION_RESULT_UNWRAPPER = new Converter<TransactionResult, List<Object>>() { @Override public List<Object> convert(TransactionResult transactionResult) { return transactionResult.stream().collect(Collectors.toList()); } }; } public static List<Tuple> toTuple(List<byte[]> list) { return BYTES_LIST_TO_TUPLE_LIST_CONVERTER.convert(list); } public static Converter<List<byte[]>, List<Tuple>> bytesListToTupleListConverter() { return BYTES_LIST_TO_TUPLE_LIST_CONVERTER; } public static Point geoCoordinatesToPoint(GeoCoordinates geoCoordinates) { return GEO_COORDINATE_TO_POINT_CONVERTER.convert(geoCoordinates); } public static Converter<String, List<RedisClientInfo>> stringToRedisClientListConverter() { return new Converter<String, List<RedisClientInfo>>() { @Override public List<RedisClientInfo> convert(String source) { if (!StringUtils.hasText(source)) { return Collections.emptyList(); } return STRING_TO_LIST_OF_CLIENT_INFO.convert(source.split("\\r?\\n")); } }; } public static Converter<Date, Long> dateToLong() { return DATE_TO_LONG; } public static Converter<List<byte[]>, Set<byte[]>> bytesListToBytesSet() { return BYTES_LIST_TO_BYTES_SET; } public static Converter<byte[], String> bytesToString() { return BYTES_TO_STRING; } public static Converter<KeyValue<byte[], byte[]>, List<byte[]>> keyValueToBytesList() { return KEY_VALUE_TO_BYTES_LIST; } public static Converter<Collection<byte[]>, List<byte[]>> bytesSetToBytesList() { return BYTES_COLLECTION_TO_BYTES_LIST; } public static Converter<Collection<byte[]>, List<byte[]>> bytesCollectionToBytesList() { return BYTES_COLLECTION_TO_BYTES_LIST; } public static Converter<List<ScoredValue<byte[]>>, Set<Tuple>> scoredValuesToTupleSet() { return SCORED_VALUES_TO_TUPLE_SET; } public static Converter<List<ScoredValue<byte[]>>, List<Tuple>> scoredValuesToTupleList() { return SCORED_VALUES_TO_TUPLE_LIST; } public static Converter<ScoredValue<byte[]>, Tuple> scoredValueToTuple() { return SCORED_VALUE_TO_TUPLE; } public static Converter<Exception, DataAccessException> exceptionConverter() { return EXCEPTION_CONVERTER; } /** * @return * @sice 1.3 */ public static Converter<Long, Boolean> longToBooleanConverter() { return LONG_TO_BOOLEAN; } public static Long toLong(Date source) { return DATE_TO_LONG.convert(source); } public static Set<byte[]> toBytesSet(List<byte[]> source) { return BYTES_LIST_TO_BYTES_SET.convert(source); } public static List<byte[]> toBytesList(KeyValue<byte[], byte[]> source) { return KEY_VALUE_TO_BYTES_LIST.convert(source); } public static List<byte[]> toBytesList(Collection<byte[]> source) { return BYTES_COLLECTION_TO_BYTES_LIST.convert(source); } public static Set<Tuple> toTupleSet(List<ScoredValue<byte[]>> source) { return SCORED_VALUES_TO_TUPLE_SET.convert(source); } public static Tuple toTuple(ScoredValue<byte[]> source) { return SCORED_VALUE_TO_TUPLE.convert(source); } public static String toString(byte[] source) { return BYTES_TO_STRING.convert(source); } public static ScriptOutputType toScriptOutputType(ReturnType returnType) { switch (returnType) { case BOOLEAN: return ScriptOutputType.BOOLEAN; case MULTI: return ScriptOutputType.MULTI; case VALUE: return ScriptOutputType.VALUE; case INTEGER: return ScriptOutputType.INTEGER; case STATUS: return ScriptOutputType.STATUS; default: throw new IllegalArgumentException("Return type " + returnType + " is not a supported script output type"); } } public static boolean toBoolean(Position where) { Assert.notNull(where, "list positions are mandatory"); return (Position.AFTER.equals(where) ? false : true); } public static int toInt(boolean value) { return (value ? 1 : 0); } public static Map<byte[], byte[]> toMap(List<byte[]> source) { return BYTES_LIST_TO_MAP.convert(source); } public static Converter<List<byte[]>, Map<byte[], byte[]>> bytesListToMapConverter() { return BYTES_LIST_TO_MAP; } public static SortArgs toSortArgs(SortParameters params) { SortArgs args = new SortArgs(); if (params == null) { return args; } if (params.getByPattern() != null) { args.by(new String(params.getByPattern(), LettuceCharsets.ASCII)); } if (params.getLimit() != null) { args.limit(params.getLimit().getStart(), params.getLimit().getCount()); } if (params.getGetPattern() != null) { byte[][] pattern = params.getGetPattern(); for (byte[] bs : pattern) { args.get(new String(bs, LettuceCharsets.ASCII)); } } if (params.getOrder() != null) { if (params.getOrder() == Order.ASC) { args.asc(); } else { args.desc(); } } Boolean isAlpha = params.isAlphabetic(); if (isAlpha != null && isAlpha) { args.alpha(); } return args; } public static List<RedisClientInfo> toListOfRedisClientInformation(String clientList) { return stringToRedisClientListConverter().convert(clientList); } public static byte[][] subarray(byte[][] input, int index) { if (input.length > index) { byte[][] output = new byte[input.length - index][]; System.arraycopy(input, index, output, 0, output.length); return output; } return null; } public static String boundaryToStringForZRange(Boundary boundary, String defaultValue) { if (boundary == null || boundary.getValue() == null) { return defaultValue; } return boundaryToString(boundary, "", "("); } private static String boundaryToString(Boundary boundary, String inclPrefix, String exclPrefix) { String prefix = boundary.isIncluding() ? inclPrefix : exclPrefix; String value = null; if (boundary.getValue() instanceof byte[]) { value = toString((byte[]) boundary.getValue()); } else { value = boundary.getValue().toString(); } return prefix + value; } /** * Convert a {@link org.springframework.data.redis.connection.RedisZSetCommands.Limit} to a lettuce * {@link io.lettuce.core.Limit}. * * @param limit * @return a lettuce {@link io.lettuce.core.Limit}. * @since 2.0 */ public static io.lettuce.core.Limit toLimit(RedisZSetCommands.Limit limit) { return Limit.create(limit.getOffset(), limit.getCount()); } /** * Convert a {@link org.springframework.data.redis.connection.RedisZSetCommands.Range} to a lettuce {@link Range}. * * @param range * @return * @since 2.0 */ public static <T> Range<T> toRange(org.springframework.data.redis.connection.RedisZSetCommands.Range range) { return Range.from(lowerBoundaryOf(range), upperBoundaryOf(range)); } /** * Convert a {@link org.springframework.data.redis.connection.RedisZSetCommands.Range} to a lettuce {@link Range} and * reverse boundaries. * * @param range * @return * @since 2.0 */ public static <T> Range<T> toRevRange(org.springframework.data.redis.connection.RedisZSetCommands.Range range) { return Range.from(upperBoundaryOf(range), lowerBoundaryOf(range)); } @SuppressWarnings("unchecked") private static <T> Range.Boundary<T> lowerBoundaryOf( org.springframework.data.redis.connection.RedisZSetCommands.Range range) { return (Range.Boundary<T>) rangeToBoundaryArgumentConverter(false).convert(range); } @SuppressWarnings("unchecked") private static <T> Range.Boundary<T> upperBoundaryOf( org.springframework.data.redis.connection.RedisZSetCommands.Range range) { return (Range.Boundary<T>) rangeToBoundaryArgumentConverter(true).convert(range); } private static Converter<org.springframework.data.redis.connection.RedisZSetCommands.Range, Range.Boundary<?>> rangeToBoundaryArgumentConverter( boolean upper) { return (source) -> { Boundary sourceBoundary = upper ? source.getMax() : source.getMin(); if (sourceBoundary == null || sourceBoundary.getValue() == null) { return Range.Boundary.unbounded(); } boolean inclusive = sourceBoundary.isIncluding(); Object value = sourceBoundary.getValue(); if (value instanceof Number) { return inclusive ? Range.Boundary.including((Number) value) : Range.Boundary.excluding((Number) value); } if (value instanceof String) { if (!StringUtils.hasText((String) value) || ObjectUtils.nullSafeEquals(value, "+") || ObjectUtils.nullSafeEquals(value, "-")) { return Range.Boundary.unbounded(); } return inclusive ? Range.Boundary.including(value.toString().getBytes(LettuceCharsets.UTF8)) : Range.Boundary.excluding(value.toString().getBytes(LettuceCharsets.UTF8)); } return inclusive ? Range.Boundary.including((byte[]) value) : Range.Boundary.excluding((byte[]) value); }; } /** * @param source List of Maps containing node details from SENTINEL SLAVES or SENTINEL MASTERS. May be empty or * {@literal null}. * @return List of {@link RedisServer}'s. List is empty if List of Maps is empty. * @since 1.5 */ public static List<RedisServer> toListOfRedisServer(List<Map<String, String>> source) { if (CollectionUtils.isEmpty(source)) { return Collections.emptyList(); } List<RedisServer> sentinels = new ArrayList<>(); for (Map<String, String> info : source) { sentinels.add(RedisServer.newServerFrom(Converters.toProperties(info))); } return sentinels; } /** * @param sentinelConfiguration the sentinel configuration containing one or more sentinels and a master name. Must * not be {@literal null} * @return A {@link RedisURI} containing Redis Sentinel addresses of {@link RedisSentinelConfiguration} * @since 1.5 */ public static RedisURI sentinelConfigurationToRedisURI(RedisSentinelConfiguration sentinelConfiguration) { Assert.notNull(sentinelConfiguration, "RedisSentinelConfiguration is required"); Set<RedisNode> sentinels = sentinelConfiguration.getSentinels(); RedisURI.Builder builder = null; for (RedisNode sentinel : sentinels) { if (builder == null) { builder = RedisURI.Builder.sentinel(sentinel.getHost(), sentinel.getPort(), sentinelConfiguration.getMaster().getName()); } else { builder.withSentinel(sentinel.getHost(), sentinel.getPort()); } } return builder.build(); } public static byte[] toBytes(String source) { return STRING_TO_BYTES.convert(source); } public static byte[] toBytes(Integer source) { return String.valueOf(source).getBytes(); } public static byte[] toBytes(Long source) { return String.valueOf(source).getBytes(); } /** * @param source * @return * @since 1.6 */ public static byte[] toBytes(Double source) { return toBytes(String.valueOf(source)); } /** * Converts a given {@link Boundary} to its binary representation suitable for {@literal ZRANGEBY*} commands, despite * {@literal ZRANGEBYLEX}. * * @param boundary * @param defaultValue * @return * @since 1.6 */ public static String boundaryToBytesForZRange(Boundary boundary, byte[] defaultValue) { if (boundary == null || boundary.getValue() == null) { return toString(defaultValue); } return boundaryToBytes(boundary, new byte[] {}, toBytes("(")); } /** * Converts a given {@link Boundary} to its binary representation suitable for ZRANGEBYLEX command. * * @param boundary * @return * @since 1.6 */ public static String boundaryToBytesForZRangeByLex(Boundary boundary, byte[] defaultValue) { if (boundary == null || boundary.getValue() == null) { return toString(defaultValue); } return boundaryToBytes(boundary, toBytes("["), toBytes("(")); } private static String boundaryToBytes(Boundary boundary, byte[] inclPrefix, byte[] exclPrefix) { byte[] prefix = boundary.isIncluding() ? inclPrefix : exclPrefix; byte[] value = null; if (boundary.getValue() instanceof byte[]) { value = (byte[]) boundary.getValue(); } else if (boundary.getValue() instanceof Double) { value = toBytes((Double) boundary.getValue()); } else if (boundary.getValue() instanceof Long) { value = toBytes((Long) boundary.getValue()); } else if (boundary.getValue() instanceof Integer) { value = toBytes((Integer) boundary.getValue()); } else if (boundary.getValue() instanceof String) { value = toBytes((String) boundary.getValue()); } else { throw new IllegalArgumentException(String.format("Cannot convert %s to binary format", boundary.getValue())); } ByteBuffer buffer = ByteBuffer.allocate(prefix.length + value.length); buffer.put(prefix); buffer.put(value); return toString(ByteUtils.getBytes(buffer)); } public static List<RedisClusterNode> partitionsToClusterNodes(Partitions partitions) { return PARTITIONS_TO_CLUSTER_NODES.convert(partitions); } /** * @param source * @return * @since 1.7 */ public static RedisClusterNode toRedisClusterNode(io.lettuce.core.cluster.models.partitions.RedisClusterNode source) { return CLUSTER_NODE_TO_CLUSTER_NODE_CONVERTER.convert(source); } /** * Converts a given {@link Expiration} and {@link SetOption} to the according {@link SetArgs}.<br /> * * @param expiration can be {@literal null}. * @param option can be {@literal null}. * @since 1.7 */ public static SetArgs toSetArgs(Expiration expiration, SetOption option) { SetArgs args = new SetArgs(); if (expiration != null && !expiration.isPersistent()) { switch (expiration.getTimeUnit()) { case SECONDS: args.ex(expiration.getExpirationTime()); break; default: args.px(expiration.getConverted(TimeUnit.MILLISECONDS)); break; } } if (option != null) { switch (option) { case SET_IF_ABSENT: args.nx(); break; case SET_IF_PRESENT: args.xx(); break; default: break; } } return args; } static Converter<List<byte[]>, Long> toTimeConverter() { return BYTES_LIST_TO_TIME_CONVERTER; } /** * Convert {@link Metric} into {@link GeoArgs.Unit}. * * @param metric * @return * @since 1.8 */ public static GeoArgs.Unit toGeoArgsUnit(Metric metric) { Metric metricToUse = metric == null || ObjectUtils.nullSafeEquals(Metrics.NEUTRAL, metric) ? DistanceUnit.METERS : metric; return ObjectUtils.caseInsensitiveValueOf(GeoArgs.Unit.values(), metricToUse.getAbbreviation()); } /** * Convert {@link GeoRadiusCommandArgs} into {@link GeoArgs}. * * @param args * @return * @since 1.8 */ public static GeoArgs toGeoArgs(GeoRadiusCommandArgs args) { GeoArgs geoArgs = new GeoArgs(); if (args.hasFlags()) { for (GeoRadiusCommandArgs.Flag flag : args.getFlags()) { switch (flag) { case WITHCOORD: geoArgs.withCoordinates(); break; case WITHDIST: geoArgs.withDistance(); break; } } } if (args.hasSortDirection()) { switch (args.getSortDirection()) { case ASC: geoArgs.asc(); break; case DESC: geoArgs.desc(); break; } } if (args.hasLimit()) { geoArgs.withCount(args.getLimit()); } return geoArgs; } /** * Get {@link Converter} capable of {@link Set} of {@link Byte} into {@link GeoResults}. * * @return * @since 1.8 */ public static Converter<Set<byte[]>, GeoResults<GeoLocation<byte[]>>> bytesSetToGeoResultsConverter() { return new Converter<Set<byte[]>, GeoResults<GeoLocation<byte[]>>>() { @Override public GeoResults<GeoLocation<byte[]>> convert(Set<byte[]> source) { if (CollectionUtils.isEmpty(source)) { return new GeoResults<>(Collections.<GeoResult<GeoLocation<byte[]>>> emptyList()); } List<GeoResult<GeoLocation<byte[]>>> results = new ArrayList<>(source.size()); Iterator<byte[]> it = source.iterator(); while (it.hasNext()) { results.add(new GeoResult<>(new GeoLocation<>(it.next(), null), new Distance(0D))); } return new GeoResults<>(results); } }; } /** * Get {@link Converter} capable of convering {@link GeoWithin} into {@link GeoResults}. * * @param metric * @return * @since 1.8 */ public static Converter<List<GeoWithin<byte[]>>, GeoResults<GeoLocation<byte[]>>> geoRadiusResponseToGeoResultsConverter( Metric metric) { return GeoResultsConverterFactory.INSTANCE.forMetric(metric); } /** * @return * @since 1.8 */ public static ListConverter<io.lettuce.core.GeoCoordinates, Point> geoCoordinatesToPointConverter() { return GEO_COORDINATE_LIST_TO_POINT_LIST_CONVERTER; } /** * @return * @since 2.0 */ @SuppressWarnings("unchecked") public static <K, V> ListConverter<KeyValue<K, V>, V> keyValueListUnwrapper() { return (ListConverter) KEY_VALUE_LIST_UNWRAPPER; } public static Converter<TransactionResult, List<Object>> transactionResultUnwrapper() { return TRANSACTION_RESULT_UNWRAPPER; } /** * @author Christoph Strobl * @since 1.8 */ static enum GeoResultsConverterFactory { INSTANCE; Converter<List<GeoWithin<byte[]>>, GeoResults<GeoLocation<byte[]>>> forMetric(Metric metric) { return new GeoResultsConverter( metric == null || ObjectUtils.nullSafeEquals(Metrics.NEUTRAL, metric) ? DistanceUnit.METERS : metric); } private static class GeoResultsConverter implements Converter<List<GeoWithin<byte[]>>, GeoResults<GeoLocation<byte[]>>> { private Metric metric; public GeoResultsConverter(Metric metric) { this.metric = metric; } @Override public GeoResults<GeoLocation<byte[]>> convert(List<GeoWithin<byte[]>> source) { List<GeoResult<GeoLocation<byte[]>>> results = new ArrayList<>(source.size()); Converter<GeoWithin<byte[]>, GeoResult<GeoLocation<byte[]>>> converter = GeoResultConverterFactory.INSTANCE .forMetric(metric); for (GeoWithin<byte[]> result : source) { results.add(converter.convert(result)); } return new GeoResults<>(results, metric); } } } /** * @author Christoph Strobl * @since 1.8 */ static enum GeoResultConverterFactory { INSTANCE; Converter<GeoWithin<byte[]>, GeoResult<GeoLocation<byte[]>>> forMetric(Metric metric) { return new GeoResultConverter(metric); } private static class GeoResultConverter implements Converter<GeoWithin<byte[]>, GeoResult<GeoLocation<byte[]>>> { private Metric metric; public GeoResultConverter(Metric metric) { this.metric = metric; } @Override public GeoResult<GeoLocation<byte[]>> convert(GeoWithin<byte[]> source) { Point point = GEO_COORDINATE_TO_POINT_CONVERTER.convert(source.getCoordinates()); return new GeoResult<>(new GeoLocation<>(source.getMember(), point), new Distance(source.getDistance() != null ? source.getDistance() : 0D, metric)); } } } }