DATAREDIS-525 - Add support for ReactiveRedisConnection & ReactiveRedisClusterConnection.

We introduced reactor based reactive connection support via ReactiveRedisConnection and ReactiveRedisClusterConnection.

A reactive connection can be obtained via the ConnectionFactory. This is currently only possible when using the Lettuce Redis driver.

Original pull request: #229.
This commit is contained in:
Christoph Strobl
2016-06-29 21:11:17 +02:00
committed by Mark Paluch
parent f6411b3811
commit d257ae1074
72 changed files with 14189 additions and 36 deletions

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.data.redis.connection;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.util.Assert;
/**
@@ -52,6 +56,33 @@ public final class ClusterSlotHashUtil {
}
/**
* @param keys must not be {@literal null}.
* @return
* @since 2.0
*/
public static boolean isSameSlotForAllKeys(Collection<ByteBuffer> keys) {
Assert.notNull(keys, "Keys must not be null!");
if (keys.size() <= 1) {
return true;
}
return isSameSlotForAllKeys((byte[][]) keys.stream().map(ByteBuffer::array).toArray(size -> new byte[size][]));
}
/**
* @param keys must not be {@literal null}.
* @return
* @since 2.0
*/
public static boolean isSameSlotForAllKeys(ByteBuffer... keys) {
Assert.notNull(keys, "Keys must not be null!");
return isSameSlotForAllKeys(Arrays.asList(keys));
}
/**
* @param keys must not be {@literal null}.
* @return

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterGeoCommands extends ReactiveGeoCommands {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterHashCommands extends ReactiveHashCommands {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterHyperLogLogCommands extends ReactiveHyperLogLogCommands {
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2016. 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.
*/
/*
* Copyright 2016. 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;
import java.nio.ByteBuffer;
import java.util.List;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterKeyCommands extends ReactiveKeyCommands {
/**
* Retrieve all {@literal keys} for a given {@literal pattern} from {@link RedisNode}.
*
* @param node must not be {@literal null}.
* @param pattern must not be {@literal null}.
* @return
*/
Mono<List<ByteBuffer>> keys(RedisClusterNode node, ByteBuffer pattern);
/**
* Retrieve a random {@literal key} from {@link RedisNode}.
*
* @param node must not be {@literal null}.
* @return
*/
Mono<ByteBuffer> randomKey(RedisClusterNode node);
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterListCommands extends ReactiveListCommands {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterNumberCommands extends ReactiveNumberCommands {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterSetCommands extends ReactiveSetCommands {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterStringCommands extends ReactiveStringCommands {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveClusterZSetCommands extends ReactiveZSetCommands {
}

View File

@@ -0,0 +1,771 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
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.RedisGeoCommands.GeoRadiusCommandArgs.Flag;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveGeoCommands {
/**
* @author Christoph Strobl
*/
class GeoAddCommand extends KeyCommand {
private final List<GeoLocation<ByteBuffer>> geoLocations;
public GeoAddCommand(ByteBuffer key, List<GeoLocation<ByteBuffer>> geoLocations) {
super(key);
this.geoLocations = geoLocations;
}
public static GeoAddCommand location(GeoLocation<ByteBuffer> geoLocation) {
return new GeoAddCommand(null, Collections.singletonList(geoLocation));
}
public static GeoAddCommand locations(List<GeoLocation<ByteBuffer>> geoLocations) {
return new GeoAddCommand(null, new ArrayList<>(geoLocations));
}
public GeoAddCommand to(ByteBuffer key) {
return new GeoAddCommand(key, geoLocations);
}
public List<GeoLocation<ByteBuffer>> getGeoLocations() {
return geoLocations;
}
}
/**
* Add {@link Point} with given {@literal member} to {@literal key}.
*
* @param key must not be {@literal null}.
* @param point must not be {@literal null}.
* @param member must not be {@literal null}.
* @return
*/
default Mono<Long> geoAdd(ByteBuffer key, Point point, ByteBuffer member) {
Assert.notNull(key, "key must not be null");
Assert.notNull(point, "point must not be null");
Assert.notNull(member, "member must not be null");
return geoAdd(key, new GeoLocation<>(member, point));
}
/**
* Add {@link GeoLocation} to {@literal key}.
*
* @param key must not be {@literal null}.
* @param location must not be {@literal null}.
* @return
*/
default Mono<Long> geoAdd(ByteBuffer key, GeoLocation<ByteBuffer> location) {
Assert.notNull(key, "key must not be null");
Assert.notNull(location, "location must not be null");
return geoAdd(key, Collections.singletonList(location));
}
/**
* Add {@link GeoLocation} to {@literal key}.
*
* @param key must not be {@literal null}.
* @param locations must not be {@literal null}.
* @return
*/
default Mono<Long> geoAdd(ByteBuffer key, List<GeoLocation<ByteBuffer>> locations) {
Assert.notNull(key, "key must not be null");
Assert.notNull(locations, "locations must not be null");
return geoAdd(Mono.just(GeoAddCommand.locations(locations).to(key))).next().map(NumericResponse::getOutput);
}
/**
* Add {@link GeoLocation}s to {@literal key}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<GeoAddCommand, Long>> geoAdd(Publisher<GeoAddCommand> commands);
/**
* @author Christoph Strobl
*/
class GeoDistCommand extends KeyCommand {
private final ByteBuffer from;
private final ByteBuffer to;
private final Metric metric;
private GeoDistCommand(ByteBuffer key, ByteBuffer from, ByteBuffer to, Metric metric) {
super(key);
this.from = from;
this.to = to;
this.metric = metric;
}
static GeoDistCommand units(Metric unit) {
return new GeoDistCommand(null, null, null, unit);
}
public static GeoDistCommand meters() {
return units(DistanceUnit.METERS);
}
public static GeoDistCommand kiometers() {
return units(DistanceUnit.KILOMETERS);
}
public static GeoDistCommand miles() {
return units(DistanceUnit.MILES);
}
public static GeoDistCommand feet() {
return units(DistanceUnit.FEET);
}
public GeoDistCommand between(ByteBuffer from) {
return new GeoDistCommand(getKey(), from, to, metric);
}
public GeoDistCommand and(ByteBuffer to) {
return new GeoDistCommand(getKey(), from, to, metric);
}
public GeoDistCommand forKey(ByteBuffer key) {
return new GeoDistCommand(key, from, to, metric);
}
public ByteBuffer getFrom() {
return from;
}
public ByteBuffer getTo() {
return to;
}
public Optional<Metric> getMetric() {
return Optional.ofNullable(metric);
}
}
/**
* Get the {@link Distance} between {@literal from} and {@literal to}.
*
* @param key must not be {@literal null}.
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
default Mono<Distance> geoDist(ByteBuffer key, ByteBuffer from, ByteBuffer to) {
return geoDist(key, from, to, null);
}
/**
* Get the {@link Distance} between {@literal from} and {@literal to}.
*
* @param key must not be {@literal null}.
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @param metric can be {@literal null} and defaults to {@link DistanceUnit#METERS}.
* @return
*/
default Mono<Distance> geoDist(ByteBuffer key, ByteBuffer from, ByteBuffer to, Metric metric) {
Assert.notNull(key, "key must not be null");
Assert.notNull(from, "from must not be null");
Assert.notNull(to, "to must not be null");
return geoDist(Mono.just(GeoDistCommand.units(metric).between(from).and(to).forKey(key))).next()
.map(CommandResponse::getOutput);
}
/**
* Get the {@link Distance} between {@literal from} and {@literal to}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<CommandResponse<GeoDistCommand, Distance>> geoDist(Publisher<GeoDistCommand> commands);
/**
* @author Christoph Strobl
*/
class GeoHashCommand extends KeyCommand {
private final List<ByteBuffer> members;
private GeoHashCommand(ByteBuffer key, List<ByteBuffer> members) {
super(key);
this.members = members;
}
public static GeoHashCommand member(ByteBuffer member) {
return new GeoHashCommand(null, Collections.singletonList(member));
}
public static GeoHashCommand members(List<ByteBuffer> members) {
return new GeoHashCommand(null, new ArrayList<>(members));
}
public GeoHashCommand of(ByteBuffer key) {
return new GeoHashCommand(key, members);
}
public List<ByteBuffer> getMembers() {
return members;
}
}
/**
* Get geohash representation of the position for the one {@literal member}.
*
* @param key must not be {@literal null}.
* @param member must not be {@literal null}.
* @return
*/
default Mono<String> geoHash(ByteBuffer key, ByteBuffer member) {
Assert.notNull(member, "member must not be null");
return geoHash(key, Collections.singletonList(member)).map(vals -> vals.isEmpty() ? null : vals.iterator().next());
}
/**
* Get geohash representation of the position for one or more {@literal member}s.
*
* @param key must not be {@literal null}.
* @param members must not be {@literal null}.
* @return
*/
default Mono<List<String>> geoHash(ByteBuffer key, List<ByteBuffer> members) {
Assert.notNull(key, "key must not be null");
Assert.notNull(members, "members must not be null");
return geoHash(Mono.just(GeoHashCommand.members(members).of(key))).next().map(MultiValueResponse::getOutput);
}
/**
* Get geohash representation of the position for one or more {@literal member}s.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<GeoHashCommand, String>> geoHash(Publisher<GeoHashCommand> commands);
/**
* @author Christoph Strobl
*/
class GeoPosCommand extends KeyCommand {
private final List<ByteBuffer> members;
private GeoPosCommand(ByteBuffer key, List<ByteBuffer> members) {
super(key);
this.members = members;
}
public static GeoPosCommand member(ByteBuffer member) {
return new GeoPosCommand(null, Collections.singletonList(member));
}
public static GeoPosCommand members(List<ByteBuffer> members) {
return new GeoPosCommand(null, new ArrayList<>(members));
}
public GeoPosCommand of(ByteBuffer key) {
return new GeoPosCommand(key, members);
}
public List<ByteBuffer> getMembers() {
return members;
}
}
/**
* Get the {@link Point} representation of positions for the {@literal member}s.
*
* @param key must not be {@literal null}.
* @param member must not be {@literal null}.
* @return
*/
default Mono<Point> geoPos(ByteBuffer key, ByteBuffer member) {
Assert.notNull(member, "member must not be null");
return geoPos(key, Collections.singletonList(member)).map(vals -> vals.isEmpty() ? null : vals.iterator().next());
}
/**
* Get the {@link Point} representation of positions for one or more {@literal member}s.
*
* @param key must not be {@literal null}.
* @param members must not be {@literal null}.
* @return
*/
default Mono<List<Point>> geoPos(ByteBuffer key, List<ByteBuffer> members) {
Assert.notNull(key, "key must not be null");
Assert.notNull(members, "members must not be null");
return geoPos(Mono.just(GeoPosCommand.members(members).of(key))).next().map(MultiValueResponse::getOutput);
}
/**
* Get the {@link Point} representation of positions for one or more {@literal member}s.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<GeoPosCommand, Point>> geoPos(Publisher<GeoPosCommand> commands);
/**
* @author Christoph Strobl
*/
class GeoRadiusCommand extends KeyCommand {
private final Distance distance;
private final Point point;
private final GeoRadiusCommandArgs args;
private final ByteBuffer store;
private final ByteBuffer storeDist;
private GeoRadiusCommand(ByteBuffer key, Point point, Distance distance, GeoRadiusCommandArgs args,
ByteBuffer store, ByteBuffer storeDist) {
super(key);
this.distance = distance;
this.point = point;
this.args = args == null ? GeoRadiusCommandArgs.newGeoRadiusArgs() : args;
this.store = store;
this.storeDist = storeDist;
}
public static GeoRadiusCommand within(Distance distance) {
return new GeoRadiusCommand(null, null, distance, null, null, null);
}
public static GeoRadiusCommand withinMeters(Double distance) {
return within(new Distance(distance, DistanceUnit.METERS));
}
public static GeoRadiusCommand withinKiometers(Double distance) {
return within(new Distance(distance, DistanceUnit.KILOMETERS));
}
public static GeoRadiusCommand withinMiles(Double distance) {
return within(new Distance(distance, DistanceUnit.MILES));
}
public static GeoRadiusCommand withinFeet(Double distance) {
return within(new Distance(distance, DistanceUnit.FEET));
}
public static GeoRadiusCommand within(Circle circle) {
return within(circle.getRadius()).from(circle.getCenter());
}
public GeoRadiusCommand from(Point center) {
return new GeoRadiusCommand(getKey(), center, distance, args, store, storeDist);
}
public GeoRadiusCommand withFlag(Flag flag) {
GeoRadiusCommandArgs args = cloneArgs();
args.flags.add(flag);
return new GeoRadiusCommand(getKey(), point, distance, args, store, storeDist);
}
public GeoRadiusCommand withCoord() {
return withFlag(Flag.WITHCOORD);
}
public GeoRadiusCommand withDist() {
return withFlag(Flag.WITHDIST);
}
public GeoRadiusCommand withArgs(GeoRadiusCommandArgs args) {
return new GeoRadiusCommand(getKey(), point, distance, args, store, storeDist);
}
public GeoRadiusCommand limitTo(Long limit) {
GeoRadiusCommandArgs args = cloneArgs();
if (limit != null) {
args = args.limit(limit);
}
return new GeoRadiusCommand(getKey(), point, distance, args, store, storeDist);
}
public GeoRadiusCommand sort(Direction direction) {
GeoRadiusCommandArgs args = cloneArgs();
args.sortDirection = direction;
return new GeoRadiusCommand(getKey(), point, distance, args, store, storeDist);
}
public GeoRadiusCommand orderByDistanceAsc() {
return sort(Direction.ASC);
}
public GeoRadiusCommand orderByDistanceDesc() {
return sort(Direction.DESC);
}
public GeoRadiusCommand forKey(ByteBuffer key) {
return new GeoRadiusCommand(key, point, distance, args, store, storeDist);
}
/**
* <b>NOTE:</b> STORE option is not compatible with WITHDIST, WITHHASH and WITHCOORDS options.
*
* @param key
* @return
*/
public GeoRadiusCommand storeAt(ByteBuffer key) {
return new GeoRadiusCommand(getKey(), point, distance, args, key, storeDist);
}
/**
* <b>NOTE:</b> STOREDIST option is not compatible with WITHDIST, WITHHASH and WITHCOORDS options.
*
* @param key
* @return
*/
public GeoRadiusCommand storeDistAt(ByteBuffer key) {
return new GeoRadiusCommand(getKey(), point, distance, args, store, key);
}
public Optional<Direction> getDirection() {
return Optional.ofNullable(args.getSortDirection());
}
public Distance getDistance() {
return distance;
}
public Set<Flag> getFlags() {
return args.getFlags();
}
public Optional<Long> getLimit() {
return Optional.ofNullable(args.getLimit());
}
public Point getPoint() {
return point;
}
public Optional<ByteBuffer> getStore() {
return Optional.ofNullable(store);
}
public Optional<ByteBuffer> getStoreDist() {
return Optional.ofNullable(storeDist);
}
public Optional<GeoRadiusCommandArgs> getArgs() {
return Optional.ofNullable(args);
}
private GeoRadiusCommandArgs cloneArgs() {
if (args == null) {
return GeoRadiusCommandArgs.newGeoRadiusArgs();
}
return args.clone();
}
}
/**
* Get the {@literal member}s within the boundaries of a given {@link Circle}.
*
* @param key must not be {@literal null}.
* @param circle must not be {@literal null}.
* @return
*/
default Mono<List<GeoLocation<ByteBuffer>>> geoRadius(ByteBuffer key, Circle circle) {
return geoRadius(key, circle, null)
.map(res -> res.getContent().stream().map(val -> val.getContent()).collect(Collectors.toList()));
}
/**
* Get the {@literal member}s within the boundaries of a given {@link Circle} applying given parameters.
*
* @param key must not be {@literal null}.
* @param circle must not be {@literal null}.
* @param geoRadiusArgs can be {@literal null}.
* @return
*/
default Mono<GeoResults<GeoLocation<ByteBuffer>>> geoRadius(ByteBuffer key, Circle circle,
GeoRadiusCommandArgs geoRadiusArgs) {
Assert.notNull(key, "key must not be null");
Assert.notNull(circle, "circle must not be null");
return geoRadius(Mono.just(GeoRadiusCommand.within(circle).withArgs(geoRadiusArgs).forKey(key))).next()
.map(CommandResponse::getOutput);
}
/**
* Get the {@literal member}s within the boundaries of a given {@link Circle} applying given parameters.
*
* @param commands
* @return
*/
Flux<CommandResponse<GeoRadiusCommand, GeoResults<GeoLocation<ByteBuffer>>>> geoRadius(
Publisher<GeoRadiusCommand> commands);
/**
* @author Christoph Strobl
*/
class GeoRadiusByMemberCommand extends KeyCommand {
private final Distance distance;
private final ByteBuffer member;
private final GeoRadiusCommandArgs args;
private final ByteBuffer store;
private final ByteBuffer storeDist;
private GeoRadiusByMemberCommand(ByteBuffer key, ByteBuffer member, Distance distance, GeoRadiusCommandArgs args,
ByteBuffer store, ByteBuffer storeDist) {
super(key);
this.distance = distance;
this.member = member;
this.args = args == null ? GeoRadiusCommandArgs.newGeoRadiusArgs() : args;
this.store = store;
this.storeDist = storeDist;
}
public static GeoRadiusByMemberCommand within(Distance distance) {
return new GeoRadiusByMemberCommand(null, null, distance, GeoRadiusCommandArgs.newGeoRadiusArgs(), null, null);
}
public static GeoRadiusByMemberCommand withinMeters(Double distance) {
return within(new Distance(distance, DistanceUnit.METERS));
}
public static GeoRadiusByMemberCommand withinKiometers(Double distance) {
return within(new Distance(distance, DistanceUnit.KILOMETERS));
}
public static GeoRadiusByMemberCommand withinMiles(Double distance) {
return within(new Distance(distance, DistanceUnit.MILES));
}
public static GeoRadiusByMemberCommand withinFeet(Double distance) {
return within(new Distance(distance, DistanceUnit.FEET));
}
public GeoRadiusByMemberCommand from(ByteBuffer member) {
return new GeoRadiusByMemberCommand(getKey(), member, distance, args, store, storeDist);
}
public GeoRadiusByMemberCommand withArgs(GeoRadiusCommandArgs args) {
return new GeoRadiusByMemberCommand(getKey(), member, distance, args, store, storeDist);
}
public GeoRadiusByMemberCommand withFlag(Flag flag) {
GeoRadiusCommandArgs args = cloneArgs();
args.flags.add(flag);
return new GeoRadiusByMemberCommand(getKey(), member, distance, args, store, storeDist);
}
public GeoRadiusByMemberCommand withCoord() {
return withFlag(Flag.WITHCOORD);
}
public GeoRadiusByMemberCommand withDist() {
return withFlag(Flag.WITHDIST);
}
public GeoRadiusByMemberCommand limitTo(Long limit) {
GeoRadiusCommandArgs args = cloneArgs();
if (limit != null) {
args = args.limit(limit);
}
return new GeoRadiusByMemberCommand(getKey(), member, distance, args, store, storeDist);
}
public GeoRadiusByMemberCommand sort(Direction direction) {
GeoRadiusCommandArgs args = cloneArgs();
args.sortDirection = direction;
return new GeoRadiusByMemberCommand(getKey(), member, distance, args, store, storeDist);
}
public GeoRadiusByMemberCommand orderByDistanceAsc() {
return sort(Direction.ASC);
}
public GeoRadiusByMemberCommand orderByDistanceDesc() {
return sort(Direction.DESC);
}
public GeoRadiusByMemberCommand forKey(ByteBuffer key) {
return new GeoRadiusByMemberCommand(key, member, distance, args, store, storeDist);
}
/**
* <b>NOTE:</b> STORE option is not compatible with WITHDIST, WITHHASH and WITHCOORDS options.
*
* @param key
* @return
*/
public GeoRadiusByMemberCommand storeAt(ByteBuffer key) {
return new GeoRadiusByMemberCommand(getKey(), member, distance, args, key, storeDist);
}
/**
* <b>NOTE:</b> STOREDIST option is not compatible with WITHDIST, WITHHASH and WITHCOORDS options.
*
* @param key
* @return
*/
public GeoRadiusByMemberCommand storeDistAt(ByteBuffer key) {
return new GeoRadiusByMemberCommand(getKey(), member, distance, args, store, key);
}
public Optional<Direction> getDirection() {
return Optional.ofNullable(args.getSortDirection());
}
public Distance getDistance() {
return distance;
}
public Set<Flag> getFlags() {
return args.getFlags();
}
public Optional<Long> getLimit() {
return Optional.ofNullable(args.getLimit());
}
public ByteBuffer getMember() {
return member;
}
public Optional<ByteBuffer> getStore() {
return Optional.ofNullable(store);
}
public Optional<ByteBuffer> getStoreDist() {
return Optional.ofNullable(storeDist);
}
public Optional<GeoRadiusCommandArgs> getArgs() {
return Optional.ofNullable(args);
}
private GeoRadiusCommandArgs cloneArgs() {
if (args == null) {
return GeoRadiusCommandArgs.newGeoRadiusArgs();
}
return args.clone();
}
}
/**
* Get the {@literal member}s within given {@link Distance} from {@literal member} applying given parameters.
*
* @param key must not be {@literal null}.
* @param member must not be {@literal null}.
* @return
*/
default Mono<List<GeoLocation<ByteBuffer>>> geoRadiusByMember(ByteBuffer key, ByteBuffer member, Distance distance) {
return geoRadiusByMember(key, member, distance, null)
.map(res -> res.getContent().stream().map(val -> val.getContent()).collect(Collectors.toList()));
}
/**
* Get the {@literal member}s within given {@link Distance} from {@literal member} applying given parameters.
*
* @param key must not be {@literal null}.
* @param member must not be {@literal null}.
* @param geoRadiusArgs can be {@literal null}.
* @return
*/
default Mono<GeoResults<GeoLocation<ByteBuffer>>> geoRadiusByMember(ByteBuffer key, ByteBuffer member,
Distance distance, GeoRadiusCommandArgs geoRadiusArgs) {
Assert.notNull(key, "key must not be null");
Assert.notNull(member, "member must not be null");
Assert.notNull(distance, "distance must not be null");
return geoRadiusByMember(
Mono.just(GeoRadiusByMemberCommand.within(distance).from(member).forKey(key).withArgs(geoRadiusArgs))).next()
.map(CommandResponse::getOutput);
}
/**
* Get the {@literal member}s within given {@link Distance} from {@literal member} applying given parameters.
*
* @param commands
* @return
*/
Flux<CommandResponse<GeoRadiusByMemberCommand, GeoResults<GeoLocation<ByteBuffer>>>> geoRadiusByMember(
Publisher<GeoRadiusByMemberCommand> commands);
}

View File

@@ -0,0 +1,414 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.reactivestreams.Publisher;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveHashCommands {
/**
* @author Christoph Strobl
*/
class HSetCommand extends KeyCommand {
private static final ByteBuffer SINGLE_VALUE_KEY = ByteBuffer.allocate(0);
private final Map<ByteBuffer, ByteBuffer> fieldValueMap;
private final Boolean upsert;
private HSetCommand(ByteBuffer key, Map<ByteBuffer, ByteBuffer> keyValueMap, Boolean upsert) {
super(key);
this.fieldValueMap = keyValueMap;
this.upsert = upsert;
}
public static HSetCommand value(ByteBuffer value) {
return new HSetCommand(null, Collections.singletonMap(SINGLE_VALUE_KEY, value), Boolean.TRUE);
}
public static HSetCommand fieldValues(Map<ByteBuffer, ByteBuffer> fieldValueMap) {
return new HSetCommand(null, fieldValueMap, Boolean.TRUE);
}
public HSetCommand ofField(ByteBuffer field) {
if (!fieldValueMap.containsKey(SINGLE_VALUE_KEY)) {
throw new InvalidDataAccessApiUsageException("Value has not been set.");
}
return new HSetCommand(getKey(), Collections.singletonMap(field, fieldValueMap.get(SINGLE_VALUE_KEY)), upsert);
}
public HSetCommand forKey(ByteBuffer key) {
return new HSetCommand(key, fieldValueMap, upsert);
}
public HSetCommand ifValueNotExists() {
return new HSetCommand(getKey(), fieldValueMap, Boolean.FALSE);
}
public Boolean isUpsert() {
return upsert;
}
public Map<ByteBuffer, ByteBuffer> getFieldValueMap() {
return fieldValueMap;
}
}
/**
* Set the {@code value} of a hash {@code field}.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Boolean> hSet(ByteBuffer key, ByteBuffer field, ByteBuffer value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(field, "field must not be null");
Assert.notNull(value, "value must not be null");
return hSet(Mono.just(HSetCommand.value(value).ofField(field).forKey(key))).next().map(BooleanResponse::getOutput);
}
/**
* Set the {@code value} of a hash {@code field}.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Boolean> hSetNX(ByteBuffer key, ByteBuffer field, ByteBuffer value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(field, "field must not be null");
Assert.notNull(value, "value must not be null");
return hSet(Mono.just(HSetCommand.value(value).ofField(field).forKey(key).ifValueNotExists())).next()
.map(BooleanResponse::getOutput);
}
/**
* Set multiple hash fields to multiple values using data provided in {@code fieldValueMap}.
*
* @param key must not be {@literal null}.
* @param fieldValueMap must not be {@literal null}.
* @return
*/
default Mono<Boolean> hMSet(ByteBuffer key, Map<ByteBuffer, ByteBuffer> fieldValueMap) {
Assert.notNull(key, "key must not be null");
Assert.notNull(fieldValueMap, "field must not be null");
return hSet(Mono.just(HSetCommand.fieldValues(fieldValueMap).forKey(key).ifValueNotExists())).next()
.map(BooleanResponse::getOutput);
}
/**
* Set the {@code value} of a hash {@code field}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<HSetCommand>> hSet(Publisher<HSetCommand> commands);
/**
* @author Christoph Strobl
*/
class HGetCommand extends KeyCommand {
private List<ByteBuffer> fields;
private HGetCommand(ByteBuffer key, List<ByteBuffer> fields) {
super(key);
this.fields = fields;
}
public static HGetCommand field(ByteBuffer field) {
return new HGetCommand(null, Collections.singletonList(field));
}
public static HGetCommand fields(List<ByteBuffer> fields) {
return new HGetCommand(null, new ArrayList<>(fields));
}
public HGetCommand from(ByteBuffer key) {
return new HGetCommand(key, fields);
}
public List<ByteBuffer> getFields() {
return fields;
}
}
/**
* Get value for given {@code field} from hash at {@code key}.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> hGet(ByteBuffer key, ByteBuffer field) {
return hMGet(key, Collections.singletonList(field)).map(val -> val.isEmpty() ? null : val.iterator().next());
}
/**
* Get values for given {@code fields} from hash at {@code key}.
*
* @param key must not be {@literal null}.
* @param fields must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> hMGet(ByteBuffer key, List<ByteBuffer> fields) {
Assert.notNull(key, "key must not be null");
Assert.notNull(fields, "fields must not be null");
return hMGet(Mono.just(HGetCommand.fields(fields).from(key))).next().map(MultiValueResponse::getOutput);
}
/**
* Get values for given {@code fields} from hash at {@code key}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<HGetCommand, ByteBuffer>> hMGet(Publisher<HGetCommand> commands);
/**
* @author Christoph Strobl
*/
class HExistsCommand extends KeyCommand {
private final ByteBuffer field;
private HExistsCommand(ByteBuffer key, ByteBuffer field) {
super(key);
this.field = field;
}
public static HExistsCommand field(ByteBuffer field) {
return new HExistsCommand(null, field);
}
public HExistsCommand in(ByteBuffer key) {
return new HExistsCommand(key, field);
}
public ByteBuffer getField() {
return field;
}
}
/**
* Determine if given hash {@code field} exists.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @return
*/
default Mono<Boolean> hExists(ByteBuffer key, ByteBuffer field) {
Assert.notNull(key, "key must not be null");
Assert.notNull(field, "field must not be null");
return hExists(Mono.just(HExistsCommand.field(field).in(key))).next().map(BooleanResponse::getOutput);
}
/**
* Determine if given hash {@code field} exists.
*
* @param commands
* @return
*/
Flux<BooleanResponse<HExistsCommand>> hExists(Publisher<HExistsCommand> commands);
/**
* @author Christoph Strobl
*/
class HDelCommand extends KeyCommand {
private final List<ByteBuffer> fields;
private HDelCommand(ByteBuffer key, List<ByteBuffer> fields) {
super(key);
this.fields = fields;
}
public static HDelCommand field(ByteBuffer field) {
return new HDelCommand(null, Collections.singletonList(field));
}
public static HDelCommand fields(List<ByteBuffer> fields) {
return new HDelCommand(null, new ArrayList<>(fields));
}
public HDelCommand from(ByteBuffer key) {
return new HDelCommand(key, fields);
}
public List<ByteBuffer> getFields() {
return fields;
}
}
/**
* Delete given hash {@code field}.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @return
*/
default Mono<Boolean> hDel(ByteBuffer key, ByteBuffer field) {
Assert.notNull(field, "field must not be null");
return hDel(key, Collections.singletonList(field)).map(val -> val > 0 ? Boolean.TRUE : Boolean.FALSE);
}
/**
* Delete given hash {@code fields}.
*
* @param key must not be {@literal null}.
* @param fields must not be {@literal null}.
* @return
*/
default Mono<Long> hDel(ByteBuffer key, List<ByteBuffer> fields) {
Assert.notNull(key, "key must not be null");
Assert.notNull(fields, "fields must not be null");
return hDel(Mono.just(HDelCommand.fields(fields).from(key))).next().map(NumericResponse::getOutput);
}
/**
* Delete given hash {@code fields}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<HDelCommand, Long>> hDel(Publisher<HDelCommand> commands);
/**
* Get size of hash at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> hLen(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return hLen(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput);
}
/**
* Get size of hash at {@code key}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<KeyCommand, Long>> hLen(Publisher<KeyCommand> commands);
/**
* Get key set (fields) of hash at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> hKeys(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return hKeys(Mono.just(new KeyCommand(key))).next().map(MultiValueResponse::getOutput);
}
/**
* Get key set (fields) of hash at {@code key}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<KeyCommand, ByteBuffer>> hKeys(Publisher<KeyCommand> commands);
/**
* Get entry set (values) of hash at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> hVals(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return hVals(Mono.just(new KeyCommand(key))).next().map(MultiValueResponse::getOutput);
}
/**
* Get entry set (values) of hash at {@code key}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<KeyCommand, ByteBuffer>> hVals(Publisher<KeyCommand> commands);
/**
* Get entire hash stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Map<ByteBuffer, ByteBuffer>> hGetAll(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return hGetAll(Mono.just(new KeyCommand(key))).next().map(CommandResponse::getOutput);
}
/**
* Get entire hash stored at {@code key}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<CommandResponse<KeyCommand, Map<ByteBuffer, ByteBuffer>>> hGetAll(Publisher<KeyCommand> commands);
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.Command;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveHyperLogLogCommands {
/**
* @author Christoph Strobl
*/
class PfAddCommand extends KeyCommand {
private final List<ByteBuffer> values;
private PfAddCommand(ByteBuffer key, List<ByteBuffer> values) {
super(key);
this.values = values;
}
public static PfAddCommand value(ByteBuffer value) {
return values(Collections.singletonList(value));
}
public static PfAddCommand values(List<ByteBuffer> values) {
return new PfAddCommand(null, new ArrayList<>(values));
}
public PfAddCommand to(ByteBuffer key) {
return new PfAddCommand(key, values);
}
public List<ByteBuffer> getValues() {
return values;
}
}
/**
* Adds given {@literal value} to the HyperLogLog stored at given {@literal key}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Long> pfAdd(ByteBuffer key, ByteBuffer value) {
Assert.notNull(value, "value must not be null");
return pfAdd(key, Collections.singletonList(value));
}
/**
* Adds given {@literal values} to the HyperLogLog stored at given {@literal key}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> pfAdd(ByteBuffer key, List<ByteBuffer> values) {
Assert.notNull(key, "key must not be null");
Assert.notNull(values, "values must not be null");
return pfAdd(Mono.just(PfAddCommand.values(values).to(key))).next().map(NumericResponse::getOutput);
}
/**
* Adds given {@literal values} to the HyperLogLog stored at given {@literal key}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<PfAddCommand, Long>> pfAdd(Publisher<PfAddCommand> commands);
/**
* @author Christoph Strobl
*/
class PfCountCommand implements Command {
private final List<ByteBuffer> keys;
private PfCountCommand(List<ByteBuffer> keys) {
super();
this.keys = keys;
}
public static PfCountCommand valueIn(ByteBuffer key) {
return valuesIn(Collections.singletonList(key));
}
public static PfCountCommand valuesIn(List<ByteBuffer> keys) {
return new PfCountCommand(keys);
}
public List<ByteBuffer> getKeys() {
return keys;
}
@Override
public ByteBuffer getKey() {
return null;
}
}
/**
* Return the approximated cardinality of the structures observed by the HyperLogLog at {@literal key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> pfCount(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return pfCount(Collections.singletonList(key));
}
/**
* Return the approximated cardinality of the structures observed by the HyperLogLog at {@literal key(s)}.
*
* @param keys must not be {@literal null}.
* @return
*/
default Mono<Long> pfCount(List<ByteBuffer> keys) {
Assert.notNull(keys, "keys must not be null");
return pfCount(Mono.just(PfCountCommand.valuesIn(keys))).next().map(NumericResponse::getOutput);
}
/**
* Return the approximated cardinality of the structures observed by the HyperLogLog at {@literal key(s)}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<PfCountCommand, Long>> pfCount(Publisher<PfCountCommand> commands);
/**
* @author Christoph Strobl
*/
class PfMergeCommand extends KeyCommand {
private final List<ByteBuffer> sourceKeys;
private PfMergeCommand(ByteBuffer key, List<ByteBuffer> sourceKeys) {
super(key);
this.sourceKeys = sourceKeys;
}
public static PfMergeCommand valuesIn(List<ByteBuffer> sourceKeys) {
return new PfMergeCommand(null, sourceKeys);
}
public PfMergeCommand into(ByteBuffer destinationKey) {
return new PfMergeCommand(destinationKey, sourceKeys);
}
public List<ByteBuffer> getSourceKeys() {
return sourceKeys;
}
}
/**
* Merge N different HyperLogLogs at {@literal sourceKeys} into a single {@literal destinationKey}.
*
* @param destinationKey must not be {@literal null}.
* @param sourceKeys must not be {@literal null}.
* @return
*/
default Mono<Boolean> pfMerge(ByteBuffer destinationKey, List<ByteBuffer> sourceKeys) {
Assert.notNull(destinationKey, "destinationKey must not be null");
Assert.notNull(sourceKeys, "sourceKeys must not be null");
return pfMerge(Mono.just(PfMergeCommand.valuesIn(sourceKeys).into(destinationKey))).next()
.map(BooleanResponse::getOutput);
}
/**
* Merge N different HyperLogLogs at {@literal sourceKeys} into a single {@literal destinationKey}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<PfMergeCommand>> pfMerge(Publisher<PfMergeCommand> commands);
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveKeyCommands {
/**
* Determine if given {@code key} exists.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Boolean> exists(ByteBuffer key) {
Assert.notNull(key, "Key must not be null!");
return exists(Mono.just(new KeyCommand(key))).next().map(BooleanResponse::getOutput);
}
/**
* Determine if given {@code key} exists.
*
* @param keys must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<KeyCommand>> exists(Publisher<KeyCommand> keys);
/**
* Determine the type stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<DataType> type(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return type(Mono.just(new KeyCommand(key))).next().map(CommandResponse::getOutput);
}
/**
* Determine the type stored at {@code key}.
*
* @param keys must not be {@literal null}.
* @return
*/
Flux<CommandResponse<KeyCommand, DataType>> type(Publisher<KeyCommand> keys);
/**
* Find all keys matching the given {@code pattern}.
*
* @param pattern must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> keys(ByteBuffer pattern) {
Assert.notNull(pattern, "pattern must not be null");
return keys(Mono.just(pattern)).next().map(MultiValueResponse::getOutput);
}
/**
* Return a random key from the keyspace.
*
* @return
*/
Mono<ByteBuffer> randomKey();
/**
* Find all keys matching the given {@code pattern}.
*
* @param patterns must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<ByteBuffer, ByteBuffer>> keys(Publisher<ByteBuffer> patterns);
/**
* @author Christoph Strobl
*/
class RenameCommand extends KeyCommand {
private ByteBuffer newName;
private RenameCommand(ByteBuffer key, ByteBuffer newName) {
super(key);
this.newName = newName;
}
public static ReactiveKeyCommands.RenameCommand key(ByteBuffer key) {
return new RenameCommand(key, null);
}
public ReactiveKeyCommands.RenameCommand to(ByteBuffer newName) {
return new RenameCommand(getKey(), newName);
}
public ByteBuffer getNewName() {
return newName;
}
}
/**
* Rename key {@code oleName} to {@code newName}.
*
* @param key must not be {@literal null}.
* @param newName must not be {@literal null}.
* @return
*/
default Mono<Boolean> rename(ByteBuffer key, ByteBuffer newName) {
Assert.notNull(key, "key must not be null");
return rename(Mono.just(RenameCommand.key(key).to(newName))).next().map(BooleanResponse::getOutput);
}
Flux<BooleanResponse<ReactiveKeyCommands.RenameCommand>> rename(Publisher<ReactiveKeyCommands.RenameCommand> cmd);
/**
* Rename key {@code oleName} to {@code newName} only if {@code newName} does not exist.
*
* @param key must not be {@literal null}.
* @param newName must not be {@literal null}.
* @return
*/
default Mono<Boolean> renameNX(ByteBuffer key, ByteBuffer newName) {
Assert.notNull(key, "key must not be null");
return renameNX(Mono.just(RenameCommand.key(key).to(newName))).next().map(BooleanResponse::getOutput);
}
/**
* Rename key {@code oleName} to {@code newName} only if {@code newName} does not exist.
*
* @param keys must not be {@literal null}.
* @param newName must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveKeyCommands.RenameCommand>> renameNX(
Publisher<ReactiveKeyCommands.RenameCommand> command);
/**
* Delete {@literal key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> del(ByteBuffer key) {
Assert.notNull(key, "Key must not be null!");
return del(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput);
}
/**
* Delete {@literal keys} one by one.
*
* @param keys must not be {@literal null}.
* @return {@link Flux} of {@link DelResponse} holding the {@literal key} removed along with the deletion result.
*/
Flux<NumericResponse<KeyCommand, Long>> del(Publisher<KeyCommand> keys);
/**
* Delete multiple {@literal keys} one in one batch.
*
* @param keys must not be {@literal null}.
* @return
*/
default Mono<Long> mDel(List<ByteBuffer> keys) {
Assert.notEmpty(keys, "Keys must not be empty or null!");
return mDel(Mono.just(keys)).next().map(NumericResponse::getOutput);
}
/**
* Delete multiple {@literal keys} in batches.
*
* @param keys must not be {@literal null}.
* @return {@link Flux} of {@link MDelResponse} holding the {@literal keys} removed along with the deletion result.
*/
Flux<NumericResponse<List<ByteBuffer>, Long>> mDel(Publisher<List<ByteBuffer>> keys);
}

View File

@@ -0,0 +1,820 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.Command;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCommand;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveListCommands {
/**
* @author Christoph Strobl
*/
enum Direction {
LEFT, RIGHT
}
/**
* @author Christoph Strobl
*/
class PushCommand extends KeyCommand {
private List<ByteBuffer> values;
private boolean upsert;
private Direction direction;
private PushCommand(ByteBuffer key, List<ByteBuffer> values, Direction direction, boolean upsert) {
super(key);
this.values = values;
this.upsert = upsert;
this.direction = direction;
}
public static PushCommand right() {
return new PushCommand(null, null, Direction.RIGHT, true);
}
public static PushCommand left() {
return new PushCommand(null, null, Direction.LEFT, true);
}
public PushCommand value(ByteBuffer value) {
return new PushCommand(null, Collections.singletonList(value), direction, upsert);
}
public PushCommand values(List<ByteBuffer> values) {
return new PushCommand(null, values, direction, upsert);
}
public PushCommand to(ByteBuffer key) {
return new PushCommand(key, values, direction, upsert);
}
public PushCommand ifExists() {
return new PushCommand(getKey(), values, direction, false);
}
public List<ByteBuffer> getValues() {
return values;
}
public boolean getUpsert() {
return upsert;
}
public Direction getDirection() {
return direction;
}
}
/**
* Append {@code values} to {@code key}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> rPush(ByteBuffer key, List<ByteBuffer> values) {
Assert.notNull(key, "command must not be null!");
Assert.notNull(values, "Values must not be null!");
return push(Mono.just(PushCommand.right().values(values).to(key))).next().map(NumericResponse::getOutput);
}
/**
* Append {@code values} to {@code key} only if {@code key} already exists.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> rPushX(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "command must not be null!");
Assert.notNull(value, "Value must not be null!");
return push(Mono.just(PushCommand.right().value(value).to(key).ifExists())).next().map(NumericResponse::getOutput);
}
/**
* Prepend {@code values} to {@code key}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> lPush(ByteBuffer key, List<ByteBuffer> values) {
Assert.notNull(key, "command must not be null!");
Assert.notNull(values, "Values must not be null!");
return push(Mono.just(PushCommand.left().values(values).to(key))).next().map(NumericResponse::getOutput);
}
/**
* Prepend {@code value} to {@code key} if {@code key} already exists.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> lPushX(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "command must not be null!");
Assert.notNull(value, "Value must not be null!");
return push(Mono.just(PushCommand.left().value(value).to(key).ifExists())).next().map(NumericResponse::getOutput);
}
/**
* Prepend {@link PushCommand#getValues()} to {@link PushCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<PushCommand, Long>> push(Publisher<PushCommand> commands);
/**
* Get the size of list stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> lLen(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return lLen(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput);
}
/**
* Get the size of list stored at {@link KeyCommand#getKey()}
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<KeyCommand, Long>> lLen(Publisher<KeyCommand> commands);
/**
* Get elements between {@code begin} and {@code end} from list at {@code key}.
*
* @param key must not be {@literal null}.
* @param start
* @param end
* @return
*/
default Mono<List<ByteBuffer>> lRange(ByteBuffer key, long start, long end) {
Assert.notNull(key, "key must not be null");
return lRange(Mono.just(RangeCommand.key(key).fromIndex(start).toIndex(end))).next()
.map(MultiValueResponse::getOutput);
}
/**
* Get elements in {@link RangeCommand#getRange()} from list at {@link RangeCommand#getKey()}
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<RangeCommand, ByteBuffer>> lRange(Publisher<RangeCommand> commands);
/**
* Trim list at {@code key} to elements between {@code begin} and {@code end}.
*
* @param key must not be {@literal null}.
* @param start
* @param end
* @return
*/
default Mono<Boolean> lTrim(ByteBuffer key, long start, long end) {
Assert.notNull(key, "key must not be null");
return lTrim(Mono.just(RangeCommand.key(key).fromIndex(start).toIndex(end))).next().map(BooleanResponse::getOutput);
}
/**
* Trim list at {@link RangeCommand#getKey()} to elements within {@link RangeCommand#getRange()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<RangeCommand>> lTrim(Publisher<RangeCommand> commands);
/**
* @author Christoph Strobl
*/
class LIndexCommand extends KeyCommand {
private final Long index;
private LIndexCommand(ByteBuffer key, Long index) {
super(key);
this.index = index;
}
public static LIndexCommand elementAt(Long index) {
return new LIndexCommand(null, index);
}
public LIndexCommand from(ByteBuffer key) {
return new LIndexCommand(key, index);
}
public Long getIndex() {
return index;
}
}
/**
* Get element at {@code index} form list at {@code key}.
*
* @param key must not be {@literal null}.
* @param index
* @return
*/
default Mono<ByteBuffer> lIndex(ByteBuffer key, long index) {
Assert.notNull(key, "key must not be null");
return lIndex(Mono.just(LIndexCommand.elementAt(index).from(key))).next().map(ByteBufferResponse::getOutput);
}
/**
* Get element at {@link LIndexCommand#getIndex()} form list at {@link LIndexCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<ByteBufferResponse<LIndexCommand>> lIndex(Publisher<LIndexCommand> commands);
/**
* @author Christoph Strobl
*/
class LInsertCommand extends KeyCommand {
private final Position position;
private final ByteBuffer pivot;
private final ByteBuffer value;
public LInsertCommand(ByteBuffer key, Position position, ByteBuffer pivot, ByteBuffer value) {
super(key);
this.position = position;
this.pivot = pivot;
this.value = value;
}
public static LInsertCommand value(ByteBuffer value) {
return new LInsertCommand(null, null, null, value);
}
public LInsertCommand before(ByteBuffer pivot) {
return new LInsertCommand(getKey(), Position.BEFORE, pivot, value);
}
public LInsertCommand after(ByteBuffer pivot) {
return new LInsertCommand(getKey(), Position.AFTER, pivot, value);
}
public LInsertCommand forKey(ByteBuffer key) {
return new LInsertCommand(key, position, pivot, value);
}
public ByteBuffer getValue() {
return value;
}
public Position getPosition() {
return position;
}
public ByteBuffer getPivot() {
return pivot;
}
}
/**
* Insert {@code value} {@link Position#BEFORE} or {@link Position#AFTER} existing {@code pivot} for {@code key}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> lInsert(ByteBuffer key, Position position, ByteBuffer pivot, ByteBuffer value) {
Assert.notNull(key, "key must not be null!");
Assert.notNull(position, "position must not be null!");
Assert.notNull(pivot, "pivot must not be null!");
Assert.notNull(value, "Value must not be null!");
LInsertCommand command = LInsertCommand.value(value);
command = Position.BEFORE.equals(position) ? command.before(pivot) : command.after(pivot);
command = command.forKey(key);
return lInsert(Mono.just(command)).next().map(NumericResponse::getOutput);
}
/**
* Insert {@link LInsertCommand#getValue()} {@link Position#BEFORE} or {@link Position#AFTER} existing
* {@link LInsertCommand#getPivot()} for {@link LInsertCommand#getKey()}
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<LInsertCommand, Long>> lInsert(Publisher<LInsertCommand> commands);
/**
* @author Christoph Strobl
*/
class LSetCommand extends KeyCommand {
private final Long index;
private final ByteBuffer value;
private LSetCommand(ByteBuffer key, Long index, ByteBuffer value) {
super(key);
this.index = index;
this.value = value;
}
public static LSetCommand elementAt(Long index) {
return new LSetCommand(null, index, null);
}
public LSetCommand to(ByteBuffer value) {
return new LSetCommand(getKey(), index, value);
}
public LSetCommand forKey(ByteBuffer key) {
return new LSetCommand(key, index, value);
}
public ByteBuffer getValue() {
return value;
}
public Long getIndex() {
return index;
}
}
/**
* Set the {@code value} list element at {@code index}.
*
* @param key must not be {@literal null}.
* @param index
* @param value must not be {@literal null}.
* @return
*/
default Mono<Boolean> lSet(ByteBuffer key, long index, ByteBuffer value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(value, "value must not be null");
return lSet(Mono.just(LSetCommand.elementAt(index).to(value).forKey(key))).next().map(BooleanResponse::getOutput);
}
/**
* Set the {@link LSetCommand#getValue()} list element at {@link LSetCommand#getKey()}.
*
* @param commands
* @return
*/
Flux<BooleanResponse<LSetCommand>> lSet(Publisher<LSetCommand> commands);
/**
* @author Christoph Strobl
*/
class LRemCommand extends KeyCommand {
private final Long count;
private final ByteBuffer value;
private LRemCommand(ByteBuffer key, Long count, ByteBuffer value) {
super(key);
this.count = count;
this.value = value;
}
public static LRemCommand all() {
return new LRemCommand(null, 0L, null);
}
public static LRemCommand first(Long count) {
return new LRemCommand(null, count, null);
}
public static LRemCommand last(Long count) {
Long value = count < 0L ? count : Math.negateExact(count);
return new LRemCommand(null, value, null);
}
public LRemCommand occurancesOf(ByteBuffer value) {
return new LRemCommand(getKey(), count, value);
}
public LRemCommand from(ByteBuffer key) {
return new LRemCommand(key, count, value);
}
public Long getCount() {
return count;
}
public ByteBuffer getValue() {
return value;
}
}
/**
* Removes all occurrences of {@code value} from the list stored at {@code key}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Long> lRem(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(value, "value must not be null");
return lRem(Mono.just(LRemCommand.all().occurancesOf(value).from(key))).next().map(NumericResponse::getOutput);
}
/**
* Removes the first {@code count} occurrences of {@code value} from the list stored at {@code key}.
*
* @param key must not be {@literal null}.
* @param count must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Long> lRem(ByteBuffer key, Long count, ByteBuffer value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(count, "count must not be null");
Assert.notNull(value, "value must not be null");
return lRem(Mono.just(LRemCommand.first(count).occurancesOf(value).from(key))).next()
.map(NumericResponse::getOutput);
}
/**
* Removes the {@link LRemCommand#getCount()} occurrences of {@link LRemCommand#getValue()} from the list stored at
* {@link LRemCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<LRemCommand, Long>> lRem(Publisher<LRemCommand> commands);
/**
* @author Christoph Strobl
*/
class PopCommand extends KeyCommand {
private final Direction direction;
private PopCommand(ByteBuffer key, Direction direction) {
super(key);
this.direction = direction;
}
public static PopCommand right() {
return new PopCommand(null, Direction.RIGHT);
}
public static PopCommand left() {
return new PopCommand(null, Direction.LEFT);
}
public PopCommand from(ByteBuffer key) {
return new PopCommand(key, direction);
}
public Direction getDirection() {
return direction;
}
}
/**
* Removes and returns first element in list stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> lPop(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return pop(Mono.just(PopCommand.left().from(key))).next().map(ByteBufferResponse::getOutput);
}
/**
* Removes and returns last element in list stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> rPop(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return pop(Mono.just(PopCommand.right().from(key))).next().map(ByteBufferResponse::getOutput);
}
/**
* Removes and returns last element in list stored at {@link KeyCommand#getKey()}
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<ByteBufferResponse<PopCommand>> pop(Publisher<PopCommand> commands);
/**
* @author Christoph Strobl
*/
class BPopCommand implements Command {
private final List<ByteBuffer> keys;
private final Duration timeout;
private final Direction direction;
private BPopCommand(List<ByteBuffer> keys, Duration timeout, Direction direction) {
this.keys = keys;
this.timeout = timeout;
this.direction = direction;
}
public static BPopCommand right() {
return new BPopCommand(null, Duration.ZERO, Direction.RIGHT);
}
public static BPopCommand left() {
return new BPopCommand(null, Duration.ZERO, Direction.LEFT);
}
public BPopCommand from(List<ByteBuffer> keys) {
return new BPopCommand(keys, Duration.ZERO, direction);
}
public BPopCommand blockingFor(Duration timeout) {
return new BPopCommand(keys, timeout, direction);
}
@Override
public ByteBuffer getKey() {
return null;
}
public List<ByteBuffer> getKeys() {
return keys;
}
public Duration getTimeout() {
return timeout;
}
public Direction getDirection() {
return direction;
}
}
/**
* @author Christoph Strobl
*/
class PopResult {
private final List<ByteBuffer> result;
public PopResult(List<ByteBuffer> result) {
this.result = result;
}
public ByteBuffer getKey() {
return ObjectUtils.isEmpty(result) ? null : result.get(0);
}
public ByteBuffer getValue() {
return ObjectUtils.isEmpty(result) ? null : result.get(1);
}
public List<ByteBuffer> getRaw() {
return Collections.unmodifiableList(result);
}
}
/**
* @author Christoph Strobl
*/
class PopResponse extends CommandResponse<BPopCommand, PopResult> {
public PopResponse(BPopCommand input, PopResult output) {
super(input, output);
}
}
/**
* Removes and returns first element from lists stored at {@code keys}. <br>
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param keys must not be {@literal null}.
* @param timeout must not be {@literal null}.
* @return
*/
default Mono<PopResult> blPop(List<ByteBuffer> keys, Duration timeout) {
Assert.notNull(keys, "keys must not be null.");
Assert.notNull(timeout, "timeout must not be null.");
return bPop(Mono.just(BPopCommand.left().from(keys).blockingFor(timeout))).next().map(PopResponse::getOutput);
}
/**
* Removes and returns last element from lists stored at {@code keys}. <br>
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param keys must not be {@literal null}.
* @param timeout must not be {@literal null}.
* @return
*/
default Mono<PopResult> brPop(List<ByteBuffer> keys, Duration timeout) {
Assert.notNull(keys, "keys must not be null.");
Assert.notNull(timeout, "timeout must not be null.");
return bPop(Mono.just(BPopCommand.right().from(keys).blockingFor(timeout))).next().map(PopResponse::getOutput);
}
/**
* Removes and returns the top {@link BPopCommand#getDirection()} element from lists stored at
* {@link BPopCommand#getKeys()}.<br>
* <b>Blocks connection</b> until element available or {@link BPopCommand#getTimeout()} reached.
*
* @param commands
* @return
*/
Flux<PopResponse> bPop(Publisher<BPopCommand> commands);
/**
* @author Christoph Strobl
*/
class RPopLPushCommand extends KeyCommand {
private final ByteBuffer destination;
private RPopLPushCommand(ByteBuffer key, ByteBuffer destination) {
super(key);
this.destination = destination;
}
public static RPopLPushCommand from(ByteBuffer sourceKey) {
return new RPopLPushCommand(sourceKey, null);
}
public RPopLPushCommand to(ByteBuffer destinationKey) {
return new RPopLPushCommand(getKey(), destinationKey);
}
public ByteBuffer getDestination() {
return destination;
}
}
/**
* Remove the last element from list at {@code source}, append it to {@code destination} and return its value.
*
* @param source must not be {@literal null}.
* @param destination must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> rPopLPush(ByteBuffer source, ByteBuffer destination) {
Assert.notNull(source, "source must not be null");
Assert.notNull(destination, "destination must not be null");
return rPopLPush(Mono.just(RPopLPushCommand.from(source).to(destination))).next()
.map(ByteBufferResponse::getOutput);
}
/**
* Remove the last element from list at {@link RPopLPushCommand#getKey()}, append it to
* {@link RPopLPushCommand#getDestination()} and return its value.
*
* @param source must not be {@literal null}.
* @param destination must not be {@literal null}.
* @return
*/
Flux<ByteBufferResponse<RPopLPushCommand>> rPopLPush(Publisher<RPopLPushCommand> commands);
/**
* @author Christoph Strobl
*/
class BRPopLPushCommand extends KeyCommand {
private final ByteBuffer destination;
private final Duration timeout;
private BRPopLPushCommand(ByteBuffer key, ByteBuffer destination, Duration timeout) {
super(key);
this.destination = destination;
this.timeout = timeout;
}
public static BRPopLPushCommand from(ByteBuffer sourceKey) {
return new BRPopLPushCommand(sourceKey, null, null);
}
public BRPopLPushCommand to(ByteBuffer destinationKey) {
return new BRPopLPushCommand(getKey(), destinationKey, timeout);
}
public BRPopLPushCommand blockingFor(Duration timeout) {
return new BRPopLPushCommand(getKey(), destination, timeout);
}
public ByteBuffer getDestination() {
return destination;
}
public Duration getTimeout() {
return timeout;
}
}
/**
* Remove the last element from list at {@code source}, append it to {@code destination} and return its value.
* <b>Blocks connection</b> until element available or {@code timeout} reached. <br />
*
* @param source must not be {@literal null}.
* @param destination must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> bRPopLPush(ByteBuffer source, ByteBuffer destination, Duration timeout) {
Assert.notNull(source, "source must not be null");
Assert.notNull(destination, "destination must not be null");
return bRPopLPush(Mono.just(BRPopLPushCommand.from(source).to(destination).blockingFor(timeout))).next()
.map(ByteBufferResponse::getOutput);
}
/**
* Remove the last element from list at {@link BRPopLPushCommand#getKey()}, append it to
* {@link BRPopLPushCommand#getDestination()} and return its value. <br />
* <b>Blocks connection</b> until element available or {@link BRPopLPushCommand#getTimeout()} reached.
*
* @param source must not be {@literal null}.
* @param destination must not be {@literal null}.
* @return
*/
Flux<ByteBufferResponse<BRPopLPushCommand>> bRPopLPush(Publisher<BRPopLPushCommand> commands);
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveNumberCommands {
/**
* Increment value of {@code key} by 1.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> incr(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return incr(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput);
}
/**
* Increment value of {@code key} by 1.
*
* @param keys must not be {@literal null}.
* @return
*/
Flux<NumericResponse<KeyCommand, Long>> incr(Publisher<KeyCommand> keys);
/**
* @author Christoph Strobl
*/
class IncrByCommand<T extends Number> extends KeyCommand {
private T value;
private IncrByCommand(ByteBuffer key, T value) {
super(key);
this.value = value;
}
public static <T extends Number> IncrByCommand<T> incr(ByteBuffer key) {
return new IncrByCommand<T>(key, null);
}
public IncrByCommand<T> by(T value) {
return new IncrByCommand<T>(getKey(), value);
}
public T getValue() {
return value;
}
}
/**
* Increment value of {@code key} by {@code value}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default <T extends Number> Mono<T> incrBy(ByteBuffer key, T value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(value, "value must not be null");
return incrBy(Mono.just(IncrByCommand.<T> incr(key).by(value))).next().map(NumericResponse::getOutput);
}
/**
* Increment value of {@code key} by {@code value}.
*
* @param keys must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
<T extends Number> Flux<NumericResponse<ReactiveNumberCommands.IncrByCommand<T>, T>> incrBy(
Publisher<ReactiveNumberCommands.IncrByCommand<T>> commands);
/**
* @author Christoph Strobl
*/
class DecrByCommand<T extends Number> extends KeyCommand {
private T value;
private DecrByCommand(ByteBuffer key, T value) {
super(key);
this.value = value;
}
public static <T extends Number> ReactiveNumberCommands.DecrByCommand<T> decr(ByteBuffer key) {
return new DecrByCommand<T>(key, null);
}
public ReactiveNumberCommands.DecrByCommand<T> by(T value) {
return new DecrByCommand<T>(getKey(), value);
}
public T getValue() {
return value;
}
}
/**
* Decrement value of {@code key} by 1.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> decr(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return decr(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput);
}
/**
* Decrement value of {@code key} by 1.
*
* @param keys must not be {@literal null}.
* @return
*/
Flux<NumericResponse<KeyCommand, Long>> decr(Publisher<KeyCommand> keys);
/**
* Decrement value of {@code key} by {@code value}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default <T extends Number> Mono<T> decrBy(ByteBuffer key, T value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(value, "value must not be null");
return decrBy(Mono.just(DecrByCommand.<T> decr(key).by(value))).next().map(NumericResponse::getOutput);
}
/**
* Decrement value of {@code key} by {@code value}.
*
* @param keys must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
<T extends Number> Flux<NumericResponse<ReactiveNumberCommands.DecrByCommand<T>, T>> decrBy(
Publisher<ReactiveNumberCommands.DecrByCommand<T>> commands);
/**
* @author Christoph Strobl
*/
class HIncrByCommand<T extends Number> extends KeyCommand {
private final ByteBuffer field;
private final T value;
private HIncrByCommand(ByteBuffer key, ByteBuffer field, T value) {
super(key);
this.field = field;
this.value = value;
}
public static <T extends Number> HIncrByCommand<T> incr(ByteBuffer field) {
return new HIncrByCommand<T>(null, field, null);
}
public HIncrByCommand<T> by(T value) {
return new HIncrByCommand<T>(getKey(), field, value);
}
public HIncrByCommand<T> forKey(ByteBuffer key) {
return new HIncrByCommand<T>(key, field, value);
}
public T getValue() {
return value;
}
public ByteBuffer getField() {
return field;
}
}
/**
* Increment {@code value} of a hash {@code field} by the given {@code value}.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default <T extends Number> Mono<T> hIncrBy(ByteBuffer key, ByteBuffer field, T value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(field, "field must not be null");
Assert.notNull(value, "value must not be null");
return hIncrBy(Mono.just(HIncrByCommand.<T> incr(field).by(value).forKey(key))).next()
.map(NumericResponse::getOutput);
}
/**
* Increment {@code value} of a hash {@code field} by the given {@code value}.
*
* @return
*/
<T extends Number> Flux<NumericResponse<HIncrByCommand<T>, T>> hIncrBy(Publisher<HIncrByCommand<T>> commands);
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2016. 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;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveRedisClusterConnection extends ReactiveRedisConnection {
@Override
ReactiveClusterKeyCommands keyCommands();
@Override
ReactiveClusterStringCommands stringCommands();
@Override
ReactiveClusterNumberCommands numberCommands();
@Override
ReactiveClusterListCommands listCommands();
@Override
ReactiveClusterSetCommands setCommands();
@Override
ReactiveClusterZSetCommands zSetCommands();
@Override
ReactiveClusterHashCommands hashCommands();
@Override
ReactiveClusterGeoCommands geoCommands();
@Override
ReactiveClusterHyperLogLogCommands hyperLogLogCommands();
}

View File

@@ -0,0 +1,256 @@
/*
* Copyright 2016 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;
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.data.domain.Range;
import lombok.Data;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveRedisConnection extends Closeable {
/**
* Get {@link ReactiveKeyCommands}.
*
* @return never {@literal null}.
*/
ReactiveKeyCommands keyCommands();
/**
* Get {@link ReactiveStringCommands}.
*
* @return never {@literal null}.
*/
ReactiveStringCommands stringCommands();
/**
* Get {@link ReactiveNumberCommands}
*
* @return never {@literal null}.
*/
ReactiveNumberCommands numberCommands();
/**
* Get {@link ReactiveListCommands}.
*
* @return never {@literal null}.
*/
ReactiveListCommands listCommands();
/**
* Get {@link ReactiveSetCommands}.
*
* @return never {@literal null}.
*/
ReactiveSetCommands setCommands();
/**
* Get {@link ReactiveZSetCommands}.
*
* @return never {@literal null}.
*/
ReactiveZSetCommands zSetCommands();
/**
* Get {@link ReactiveHashCommands}.
*
* @return
*/
ReactiveHashCommands hashCommands();
/**
* Get {@link ReacktiveGeoCommands}
*
* @return never {@literal null}.
*/
ReactiveGeoCommands geoCommands();
/**
* Get {@link ReactiveHyperLogLogCommands}.
*
* @return never {@literal null}.
*/
ReactiveHyperLogLogCommands hyperLogLogCommands();
interface Command {
ByteBuffer getKey();
default String getName() {
return getClass().getSimpleName().replace("Command", "").toUpperCase();
}
static <T extends Command> Builder<T> create(Class<T> type) {
return new CommandBuilder<T>(type);
}
interface Builder<T extends Command> extends Consumer<Object> {
default Builder<T> forKey(String key) {
return forKey(key.getBytes(Charset.forName("UTF-8")));
}
default Builder<T> forKey(byte[] key) {
return forKey(ByteBuffer.wrap(key));
}
default Builder<T> forKey(ByteBuffer key) {
return forKey(() -> key);
}
Builder<T> forKey(Supplier<ByteBuffer> keySupplier);
T build();
}
class CommandBuilder<T extends Command> implements Builder<T> {
List<Object> argumentList = new ArrayList<>();
Class<T> type;
Supplier<ByteBuffer> key;
public CommandBuilder(Class<T> type) {
this.type = type;
}
public Builder<T> forKey(Supplier<ByteBuffer> key) {
this.key = key;
return this;
}
@Override
public void accept(Object t) {
argumentList.add(t);
}
@Override
public T build() {
try {
T x = BeanUtils.instantiateClass(type);
DirectFieldAccessor dfa = new DirectFieldAccessor(x);
dfa.setPropertyValue("key", key);
return x;
} catch (IllegalArgumentException | SecurityException e) {
throw new IllegalArgumentException(" ¯\\_(ツ)_/¯", e);
}
}
}
}
/**
* @author Christoph Strobl
*/
class KeyCommand implements Command {
private ByteBuffer key;
public KeyCommand(ByteBuffer key) {
this.key = key;
}
@Override
public ByteBuffer getKey() {
return key;
}
}
/**
* @author Christoph Strobl
*/
class RangeCommand extends KeyCommand {
Range<Long> range;
public RangeCommand(ByteBuffer key, Range<Long> range) {
super(key);
this.range = range != null ? range : new Range<>(0L, Long.MAX_VALUE);
}
public static RangeCommand key(ByteBuffer key) {
return new RangeCommand(key, null);
}
public RangeCommand within(Range<Long> range) {
return new RangeCommand(getKey(), range);
}
public RangeCommand fromIndex(Long start) {
return new RangeCommand(getKey(), new Range<>(start, range.getUpperBound()));
}
public RangeCommand toIndex(Long end) {
return new RangeCommand(getKey(), new Range<>(range.getLowerBound(), end));
}
public Range<Long> getRange() {
return range;
}
}
@Data
class CommandResponse<I, O> {
private final I input;
private final O output;
}
class BooleanResponse<I> extends CommandResponse<I, Boolean> {
public BooleanResponse(I input, Boolean output) {
super(input, output);
}
}
class ByteBufferResponse<I> extends CommandResponse<I, ByteBuffer> {
public ByteBufferResponse(I input, ByteBuffer output) {
super(input, output);
}
}
class MultiValueResponse<I, O> extends CommandResponse<I, List<O>> {
public MultiValueResponse(I input, List<O> output) {
super(input, output);
}
}
class NumericResponse<I, O extends Number> extends CommandResponse<I, O> {
public NumericResponse(I input, O output) {
super(input, output);
}
}
}

View File

@@ -0,0 +1,700 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.Command;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveSetCommands {
/**
* @author Christoph Strobl
*/
class SAddCommand extends KeyCommand {
private List<ByteBuffer> values;
private SAddCommand(ByteBuffer key, List<ByteBuffer> values) {
super(key);
this.values = values;
}
public static SAddCommand value(ByteBuffer values) {
return values(Collections.singletonList(values));
}
public static SAddCommand values(List<ByteBuffer> values) {
return new SAddCommand(null, values);
}
public SAddCommand to(ByteBuffer key) {
return new SAddCommand(key, values);
}
public List<ByteBuffer> getValues() {
return values;
}
}
/**
* Add given {@code value} to set at {@code key}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Long> sAdd(ByteBuffer key, ByteBuffer value) {
Assert.notNull(value, "value must not be null");
return sAdd(key, Collections.singletonList(value));
}
/**
* Add given {@code values} to set at {@code key}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> sAdd(ByteBuffer key, List<ByteBuffer> values) {
Assert.notNull(key, "key must not be null");
Assert.notNull(values, "values must not be null");
return sAdd(Mono.just(SAddCommand.values(values).to(key))).next().map(NumericResponse::getOutput);
}
/**
* Add given {@link SAddCommand#getValues()} to set at {@link SAddCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<SAddCommand, Long>> sAdd(Publisher<SAddCommand> commands);
/**
* @author Christoph Strobl
*/
class SRemCommand extends KeyCommand {
private final List<ByteBuffer> values;
public SRemCommand(ByteBuffer key, List<ByteBuffer> values) {
super(key);
this.values = values;
}
public static SRemCommand value(ByteBuffer values) {
return values(Collections.singletonList(values));
}
public static SRemCommand values(List<ByteBuffer> values) {
return new SRemCommand(null, values);
}
public SRemCommand from(ByteBuffer key) {
return new SRemCommand(key, values);
}
public List<ByteBuffer> getValues() {
return values;
}
}
/**
* Remove given {@code value} from set at {@code key} and return the number of removed elements.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Long> sRem(ByteBuffer key, ByteBuffer value) {
Assert.notNull(value, "value must not be null");
return sRem(key, Collections.singletonList(value));
}
/**
* Remove given {@code values} from set at {@code key} and return the number of removed elements.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
default Mono<Long> sRem(ByteBuffer key, List<ByteBuffer> values) {
Assert.notNull(key, "key must not be null");
Assert.notNull(values, "values must not be null");
return sRem(Mono.just(SRemCommand.values(values).from(key))).next().map(NumericResponse::getOutput);
}
/**
* Remove given {@link SRemCommand#getValues()} from set at {@link SRemCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<SRemCommand, Long>> sRem(Publisher<SRemCommand> commands);
/**
* Remove and return a random member from set at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> sPop(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return sPop(Mono.just(new KeyCommand(key))).next().map(ByteBufferResponse::getOutput);
}
/**
* Remove and return a random member from set at {@link KeyCommand#getKey()}
*
* @param commands
* @return
*/
Flux<ByteBufferResponse<KeyCommand>> sPop(Publisher<KeyCommand> commands);
/**
* @author Christoph Strobl
*/
class SMoveCommand extends KeyCommand {
private final ByteBuffer destination;
private final ByteBuffer value;
private SMoveCommand(ByteBuffer key, ByteBuffer destination, ByteBuffer value) {
super(key);
this.destination = destination;
this.value = value;
}
public static SMoveCommand value(ByteBuffer value) {
return new SMoveCommand(null, null, value);
}
public SMoveCommand from(ByteBuffer source) {
return new SMoveCommand(source, destination, value);
}
public SMoveCommand to(ByteBuffer destination) {
return new SMoveCommand(getKey(), destination, value);
}
public ByteBuffer getDestination() {
return destination;
}
public ByteBuffer getValue() {
return value;
}
}
/**
* Move {@code value} from {@code sourceKey} to {@code destinationKey}
*
* @param sourceKey must not be {@literal null}.
* @param destinationKey must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Boolean> sMove(ByteBuffer sourceKey, ByteBuffer destinationKey, ByteBuffer value) {
Assert.notNull(sourceKey, "sourceKey must not be null");
Assert.notNull(destinationKey, "destinationKey must not be null");
Assert.notNull(value, "value must not be null");
return sMove(Mono.just(SMoveCommand.value(value).from(sourceKey).to(destinationKey))).next()
.map(BooleanResponse::getOutput);
}
/**
* Move {@link SMoveCommand#getValue()} from {@link SMoveCommand#getKey()} to {@link SMoveCommand#getDestination()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<SMoveCommand>> sMove(Publisher<SMoveCommand> commands);
/**
* Get size of set at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> sCard(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return sCard(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput);
}
/**
* Get size of set at {@link KeyCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<KeyCommand, Long>> sCard(Publisher<KeyCommand> commands);
/**
* @author Christoph Strobl
*/
class SIsMemberCommand extends KeyCommand {
private final ByteBuffer value;
private SIsMemberCommand(ByteBuffer key, ByteBuffer value) {
super(key);
this.value = value;
}
public static SIsMemberCommand value(ByteBuffer value) {
return new SIsMemberCommand(null, value);
}
public SIsMemberCommand of(ByteBuffer set) {
return new SIsMemberCommand(set, value);
}
public ByteBuffer getValue() {
return value;
}
}
/**
* Check if set at {@code key} contains {@code value}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Boolean> sIsMember(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(value, "value must not be null");
return sIsMember(Mono.just(SIsMemberCommand.value(value).of(key))).next().map(BooleanResponse::getOutput);
}
/**
* Check if set at {@link SIsMemberCommand#getKey()} contains {@link SIsMemberCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<SIsMemberCommand>> sIsMember(Publisher<SIsMemberCommand> commands);
/**
* @author Christoph Strobl
*/
class SInterCommand implements Command {
private final List<ByteBuffer> keys;
private SInterCommand(List<ByteBuffer> keys) {
this.keys = keys;
}
public static SInterCommand keys(List<ByteBuffer> keys) {
return new SInterCommand(keys);
}
@Override
public ByteBuffer getKey() {
return null;
}
public List<ByteBuffer> getKeys() {
return keys;
}
}
/**
* Returns the members intersecting all given sets at {@code keys}.
*
* @param keys must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> sInter(List<ByteBuffer> keys) {
Assert.notNull(keys, "keys must not be null");
return sInter(Mono.just(SInterCommand.keys(keys))).next().map(MultiValueResponse::getOutput);
}
/**
* Returns the members intersecting all given sets at {@link SInterCommand#getKeys()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<SInterCommand, ByteBuffer>> sInter(Publisher<SInterCommand> commands);
/**
* @author Christoph Strobl
*/
class SInterStoreCommand extends KeyCommand {
private final List<ByteBuffer> keys;
private SInterStoreCommand(ByteBuffer key, List<ByteBuffer> keys) {
super(key);
this.keys = keys;
}
public static SInterStoreCommand keys(List<ByteBuffer> keys) {
return new SInterStoreCommand(null, keys);
}
public SInterStoreCommand storeAt(ByteBuffer key) {
return new SInterStoreCommand(key, keys);
}
public List<ByteBuffer> getKeys() {
return keys;
}
}
/**
* Intersect all given sets at {@code keys} and store result in {@code destinationKey}.
*
* @param destinationKey must not be {@literal null}.
* @param keys must not be {@literal null}.
* @return size of set stored a {@code destinationKey}.
*/
default Mono<Long> sInterStore(ByteBuffer destinationKey, List<ByteBuffer> keys) {
Assert.notNull(destinationKey, "destinationKey must not be null");
Assert.notNull(keys, "keys must not be null");
return sInterStore(Mono.just(SInterStoreCommand.keys(keys).storeAt(destinationKey))).next()
.map(NumericResponse::getOutput);
}
/**
* Intersect all given sets at {@code keys} and store result in {@code destinationKey}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<SInterStoreCommand, Long>> sInterStore(Publisher<SInterStoreCommand> commands);
/**
* @author Christoph Strobl
*/
class SUnionCommand implements Command {
private final List<ByteBuffer> keys;
private SUnionCommand(List<ByteBuffer> keys) {
this.keys = keys;
}
public static SUnionCommand keys(List<ByteBuffer> keys) {
return new SUnionCommand(keys);
}
@Override
public ByteBuffer getKey() {
return null;
}
public List<ByteBuffer> getKeys() {
return keys;
}
}
/**
* Returns the members intersecting all given sets at {@code keys}.
*
* @param keys must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> sUnion(List<ByteBuffer> keys) {
Assert.notNull(keys, "keys must not be null");
return sUnion(Mono.just(SUnionCommand.keys(keys))).next().map(MultiValueResponse::getOutput);
}
/**
* Returns the members intersecting all given sets at {@link SInterCommand#getKeys()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<SUnionCommand, ByteBuffer>> sUnion(Publisher<SUnionCommand> commands);
/**
* @author Christoph Strobl
*/
class SUnionStoreCommand extends KeyCommand {
private final List<ByteBuffer> keys;
private SUnionStoreCommand(ByteBuffer key, List<ByteBuffer> keys) {
super(key);
this.keys = keys;
}
public static SUnionStoreCommand keys(List<ByteBuffer> keys) {
return new SUnionStoreCommand(null, keys);
}
public SUnionStoreCommand storeAt(ByteBuffer key) {
return new SUnionStoreCommand(key, keys);
}
public List<ByteBuffer> getKeys() {
return keys;
}
}
/**
* Union all given sets at {@code keys} and store result in {@code destinationKey}.
*
* @param destinationKey must not be {@literal null}.
* @param keys must not be {@literal null}.
* @return size of set stored a {@code destinationKey}.
*/
default Mono<Long> sUnionStore(ByteBuffer destinationKey, List<ByteBuffer> keys) {
Assert.notNull(destinationKey, "destinationKey must not be null");
Assert.notNull(keys, "keys must not be null");
return sUnionStore(Mono.just(SUnionStoreCommand.keys(keys).storeAt(destinationKey))).next()
.map(NumericResponse::getOutput);
}
/**
* Union all given sets at {@code keys} and store result in {@code destinationKey}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<SUnionStoreCommand, Long>> sUnionStore(Publisher<SUnionStoreCommand> commands);
/**
* @author Christoph Strobl
*/
class SDiffCommand implements Command {
private final List<ByteBuffer> keys;
private SDiffCommand(List<ByteBuffer> keys) {
this.keys = keys;
}
public static SDiffCommand keys(List<ByteBuffer> keys) {
return new SDiffCommand(keys);
}
@Override
public ByteBuffer getKey() {
return null;
}
public List<ByteBuffer> getKeys() {
return keys;
}
}
/**
* Returns the diff of the members of all given sets at {@code keys}.
*
* @param keys must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> sDiff(List<ByteBuffer> keys) {
Assert.notNull(keys, "keys must not be null");
return sDiff(Mono.just(SDiffCommand.keys(keys))).next().map(MultiValueResponse::getOutput);
}
/**
* Returns the diff of the members of all given sets at {@link SInterCommand#getKeys()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<SDiffCommand, ByteBuffer>> sDiff(Publisher<SDiffCommand> commands);
/**
* @author Christoph Strobl
*/
class SDiffStoreCommand extends KeyCommand {
private final List<ByteBuffer> keys;
private SDiffStoreCommand(ByteBuffer key, List<ByteBuffer> keys) {
super(key);
this.keys = keys;
}
public static SDiffStoreCommand keys(List<ByteBuffer> keys) {
return new SDiffStoreCommand(null, keys);
}
public SDiffStoreCommand storeAt(ByteBuffer key) {
return new SDiffStoreCommand(key, keys);
}
public List<ByteBuffer> getKeys() {
return keys;
}
}
/**
* Diff all given sets at {@code keys} and store result in {@code destinationKey}.
*
* @param destinationKey must not be {@literal null}.
* @param keys must not be {@literal null}.
* @return size of set stored a {@code destinationKey}.
*/
default Mono<Long> sDiffStore(ByteBuffer destinationKey, List<ByteBuffer> keys) {
Assert.notNull(destinationKey, "destinationKey must not be null");
Assert.notNull(keys, "keys must not be null");
return sDiffStore(Mono.just(SDiffStoreCommand.keys(keys).storeAt(destinationKey))).next()
.map(NumericResponse::getOutput);
}
/**
* Diff all given sets at {@code keys} and store result in {@code destinationKey}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<NumericResponse<SDiffStoreCommand, Long>> sDiffStore(Publisher<SDiffStoreCommand> commands);
/**
* Get all elements of set at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> sMembers(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return sMembers(Mono.just(new KeyCommand(key))).next().map(MultiValueResponse::getOutput);
}
/**
* Get all elements of set at {@link KeyCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<KeyCommand, ByteBuffer>> sMembers(Publisher<KeyCommand> commands);
/**
* @author Christoph Strobl
*/
class SRandMembersCommand extends KeyCommand {
private final Long count;
private SRandMembersCommand(ByteBuffer key, Long count) {
super(key);
this.count = count;
}
public static SRandMembersCommand valueCount(Long nrValuesToRetrieve) {
return new SRandMembersCommand(null, nrValuesToRetrieve);
}
public static SRandMembersCommand singleValue() {
return new SRandMembersCommand(null, null);
}
public SRandMembersCommand from(ByteBuffer key) {
return new SRandMembersCommand(key, count);
}
public Optional<Long> getCount() {
return Optional.ofNullable(count);
}
}
/**
* Get random element from set at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> sRandMember(ByteBuffer key) {
return sRandMember(key, 1L).map(vals -> vals.isEmpty() ? null : vals.iterator().next());
}
/**
* Get {@code count} random elements from set at {@code key}.
*
* @param key must not be {@literal null}.
* @param count must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> sRandMember(ByteBuffer key, Long count) {
Assert.notNull(key, "key must not be null");
Assert.notNull(count, "count must not be null");
return sRandMember(Mono.just(SRandMembersCommand.valueCount(count).from(key))).next()
.map(MultiValueResponse::getOutput);
}
/**
* Get {@link SRandMembersCommand#getCount()} random elements from set at {@link SRandMembersCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<SRandMembersCommand, ByteBuffer>> sRandMember(Publisher<SRandMembersCommand> commands);
}

View File

@@ -0,0 +1,752 @@
/*
* Copyright 2016 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;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.Command;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCommand;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveStringCommands {
/**
* @author Christoph Strobl
*/
class SetCommand extends KeyCommand {
private ByteBuffer value;
private Expiration expiration;
private SetOption option;
private SetCommand(ByteBuffer key, ByteBuffer value, Expiration expiration, SetOption option) {
super(key);
this.value = value;
this.expiration = expiration;
this.option = option;
}
public static ReactiveStringCommands.SetCommand set(ByteBuffer key) {
return new SetCommand(key, null, null, null);
}
public ReactiveStringCommands.SetCommand value(ByteBuffer value) {
return new SetCommand(getKey(), value, expiration, option);
}
public ReactiveStringCommands.SetCommand expiring(Expiration expiration) {
return new SetCommand(getKey(), value, expiration, option);
}
public ReactiveStringCommands.SetCommand withSetOption(SetOption option) {
return new SetCommand(getKey(), value, expiration, option);
}
public ByteBuffer getValue() {
return value;
}
public Optional<Expiration> getExpiration() {
return Optional.ofNullable(expiration);
}
public Optional<SetOption> getOption() {
return Optional.ofNullable(option);
}
}
/**
* Set {@literal value} for {@literal key}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Boolean> set(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return set(Mono.just(SetCommand.set(key).value(value))).next().map(BooleanResponse::getOutput);
}
/**
* Set {@literal value} for {@literal key} with {@literal expiration} and {@literal options}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param expiration must not be {@literal null}.
* @param option must not be {@literal null}.
* @return
*/
default Mono<Boolean> set(ByteBuffer key, ByteBuffer value, Expiration expiration, SetOption option) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return set(Mono.just(SetCommand.set(key).value(value).withSetOption(option).expiring(expiration))).next()
.map(BooleanResponse::getOutput);
}
/**
* Set each and every {@link KeyValue} item separately.
*
* @param values must not be {@literal null}.
* @return {@link Flux} of {@link SetResponse} holding the {@link KeyValue} pair to set along with the command result.
*/
Flux<BooleanResponse<ReactiveStringCommands.SetCommand>> set(Publisher<ReactiveStringCommands.SetCommand> commands);
/**
* Get single element stored at {@literal key}.
*
* @param key must not be {@literal null}.
* @return empty {@link ByteBuffer} in case {@literal key} does not exist.
*/
default Mono<ByteBuffer> get(ByteBuffer key) {
Assert.notNull(key, "Key must not be null!");
return get(Mono.just(new KeyCommand(key))).next().map((result) -> result.getOutput());
}
/**
* Get elements one by one.
*
* @param keys must not be {@literal null}.
* @return {@link Flux} of {@link GetResponse} holding the {@literal key} to get along with the value retrieved.
*/
Flux<ByteBufferResponse<KeyCommand>> get(Publisher<KeyCommand> keys);
/**
* Set {@literal value} for {@literal key} and return the existing value.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<ByteBuffer> getSet(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return getSet(Mono.just(SetCommand.set(key).value(value))).next().map(ByteBufferResponse::getOutput);
}
/**
* Set {@literal value} for {@literal key} and return the existing value one by one.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return {@link Flux} of {@link GetSetResponse} holding the {@link KeyValue} pair to set along with the previously
* existing value.
*/
Flux<ByteBufferResponse<ReactiveStringCommands.SetCommand>> getSet(
Publisher<ReactiveStringCommands.SetCommand> command);
/**
* Get multiple values in one batch.
*
* @param keys must not be {@literal null}.
* @return
*/
default Mono<List<ByteBuffer>> mGet(List<ByteBuffer> keys) {
Assert.notNull(keys, "Keys must not be null!");
return mGet(Mono.just(keys)).next().map(MultiValueResponse::getOutput);
}
/**
* Get multiple values at in batches.
*
* @param keys must not be {@literal null}.
* @return
*/
Flux<MultiValueResponse<List<ByteBuffer>, ByteBuffer>> mGet(Publisher<List<ByteBuffer>> keysets);
/**
* Set {@code value} for {@code key}, only if {@code key} does not exist.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Boolean> setNX(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "Keys must not be null!");
Assert.notNull(value, "Keys must not be null!");
return setNX(Mono.just(SetCommand.set(key).value(value))).next().map(BooleanResponse::getOutput);
}
/**
* Set {@code key value} pairs, only if {@code key} does not exist.
*
* @param values must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveStringCommands.SetCommand>> setNX(Publisher<ReactiveStringCommands.SetCommand> values);
/**
* Set {@code key value} pair and {@link Expiration}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param expireTimeout must not be {@literal null}.
* @return
*/
default Mono<Boolean> setEX(ByteBuffer key, ByteBuffer value, Expiration expireTimeout) {
Assert.notNull(key, "Keys must not be null!");
Assert.notNull(value, "Keys must not be null!");
Assert.notNull(key, "ExpireTimeout must not be null!");
return setEX(Mono.just(SetCommand.set(key).value(value).expiring(expireTimeout))).next()
.map(BooleanResponse::getOutput);
}
/**
* Set {@code key value} pairs and {@link Expiration}.
*
* @param source must not be {@literal null}.
* @param expireTimeout must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveStringCommands.SetCommand>> setEX(Publisher<ReactiveStringCommands.SetCommand> command);
/**
* Set {@code key value} pair and {@link Expiration}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param expireTimeout must not be {@literal null}.
* @return
*/
default Mono<Boolean> pSetEX(ByteBuffer key, ByteBuffer value, Expiration expireTimeout) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
Assert.notNull(key, "ExpireTimeout must not be null!");
return pSetEX(Mono.just(SetCommand.set(key).value(value).expiring(expireTimeout))).next()
.map(BooleanResponse::getOutput);
}
/**
* Set {@code key value} pairs and {@link Expiration}.
*
* @param source must not be {@literal null}.
* @param expireTimeout must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveStringCommands.SetCommand>> pSetEX(Publisher<ReactiveStringCommands.SetCommand> command);
/**
* @author Christoph Strobl
*/
class MSetCommand implements Command {
private Map<ByteBuffer, ByteBuffer> keyValuePairs;
private MSetCommand(Map<ByteBuffer, ByteBuffer> keyValuePairs) {
this.keyValuePairs = keyValuePairs;
}
@Override
public ByteBuffer getKey() {
return null;
}
public static ReactiveStringCommands.MSetCommand mset(Map<ByteBuffer, ByteBuffer> keyValuePairs) {
return new MSetCommand(keyValuePairs);
}
public Map<ByteBuffer, ByteBuffer> getKeyValuePairs() {
return keyValuePairs;
}
}
/**
* Set multiple keys to multiple values using key-value pairs provided in {@code tuple}.
*
* @param tuples must not be {@literal null}.
* @return
*/
default Mono<Boolean> mSet(Map<ByteBuffer, ByteBuffer> tuples) {
Assert.notNull(tuples, "Tuples must not be null!");
return mSet(Mono.just(MSetCommand.mset(tuples))).next().map(BooleanResponse::getOutput);
}
/**
* Set multiple keys to multiple values using key-value pairs provided in {@code source}.
*
* @param source must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveStringCommands.MSetCommand>> mSet(Publisher<ReactiveStringCommands.MSetCommand> source);
/**
* Set multiple keys to multiple values using key-value pairs provided in {@code tuples} only if the provided key does
* not exist.
*
* @param tuples must not be {@literal null}.
* @return
*/
default Mono<Boolean> mSetNX(Map<ByteBuffer, ByteBuffer> tuples) {
Assert.notNull(tuples, "Tuples must not be null!");
return mSetNX(Mono.just(MSetCommand.mset(tuples))).next().map(BooleanResponse::getOutput);
}
/**
* Set multiple keys to multiple values using key-value pairs provided in {@code tuples} only if the provided key does
* not exist.
*
* @param source must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveStringCommands.MSetCommand>> mSetNX(
Publisher<ReactiveStringCommands.MSetCommand> source);
/**
* @author Christoph Strobl
*/
class AppendCommand extends KeyCommand {
private ByteBuffer value;
private AppendCommand(ByteBuffer key, ByteBuffer value) {
super(key);
this.value = value;
}
public static ReactiveStringCommands.AppendCommand key(ByteBuffer key) {
return new AppendCommand(key, null);
}
public ReactiveStringCommands.AppendCommand append(ByteBuffer value) {
return new AppendCommand(getKey(), value);
}
public ByteBuffer getValue() {
return value;
}
}
/**
* Append a {@code value} to {@code key}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
default Mono<Long> append(ByteBuffer key, ByteBuffer value) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return append(Mono.just(AppendCommand.key(key).append(value))).next().map(NumericResponse::getOutput);
}
/**
* Append a {@link KeyValue#value} to {@link KeyValue#key}
*
* @param source must not be {@literal null}.
* @return
*/
Flux<NumericResponse<ReactiveStringCommands.AppendCommand, Long>> append(
Publisher<ReactiveStringCommands.AppendCommand> source);
/**
* Get a substring of value of {@code key} between {@code begin} and {@code end}.
*
* @param key must not be {@literal null}.
* @param begin
* @param end
* @return
*/
default Mono<ByteBuffer> getRange(ByteBuffer key, long begin, long end) {
Assert.notNull(key, "Key must not be null!");
return getRange(Mono.just(RangeCommand.key(key).fromIndex(begin).toIndex(end))).next()
.map(ByteBufferResponse::getOutput);
}
/**
* Get a substring of value of {@code key} between {@code begin} and {@code end}.
*
* @param keys must not be {@literal null}.
* @param begin
* @param end
* @return
*/
Flux<ByteBufferResponse<RangeCommand>> getRange(Publisher<RangeCommand> commands);
/**
* @author Christoph Strobl
*/
class SetRangeCommand extends KeyCommand {
private ByteBuffer value;
private Long offset;
private SetRangeCommand(ByteBuffer key, ByteBuffer value, Long offset) {
super(key);
this.value = value;
this.offset = offset;
}
public static ReactiveStringCommands.SetRangeCommand overwrite(ByteBuffer key) {
return new SetRangeCommand(key, null, null);
}
public ReactiveStringCommands.SetRangeCommand withValue(ByteBuffer value) {
return new SetRangeCommand(getKey(), value, offset);
}
public ReactiveStringCommands.SetRangeCommand atPosition(Long index) {
return new SetRangeCommand(getKey(), value, index);
}
public ByteBuffer getValue() {
return value;
}
public Long getOffset() {
return offset;
}
}
/**
* Overwrite parts of {@code key} starting at the specified {@code offset} with given {@code value}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param offset
* @return
*/
default Mono<Long> setRange(ByteBuffer key, ByteBuffer value, long offset) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return setRange(Mono.just(SetRangeCommand.overwrite(key).withValue(value).atPosition(offset))).next()
.map(NumericResponse::getOutput);
}
/**
* Overwrite parts of {@link KeyValue#key} starting at the specified {@code offset} with given {@link KeyValue#value}.
*
* @param keys must not be {@literal null}.
* @param offset must not be {@literal null}.
* @return
*/
Flux<NumericResponse<ReactiveStringCommands.SetRangeCommand, Long>> setRange(
Publisher<ReactiveStringCommands.SetRangeCommand> commands);
class GetBitCommand extends KeyCommand {
public Long offset;
public GetBitCommand(ByteBuffer key, Long offset) {
super(key);
this.offset = offset;
}
public static ReactiveStringCommands.GetBitCommand bit(ByteBuffer key) {
return new GetBitCommand(key, null);
}
public ReactiveStringCommands.GetBitCommand atOffset(Long offset) {
return new GetBitCommand(getKey(), offset);
}
public Long getOffset() {
return offset;
}
}
/**
* Get the bit value at {@code offset} of value at {@code key}.
*
* @param key must not be {@literal null}.
* @param offset
* @return
*/
default Mono<Boolean> getBit(ByteBuffer key, long offset) {
Assert.notNull(key, "Key must not be null!");
return getBit(Mono.just(GetBitCommand.bit(key).atOffset(offset))).next().map(BooleanResponse::getOutput);
}
/**
* Get the bit value at {@code offset} of value at {@code key}.
*
* @param keys must not be {@literal null}.
* @param offset must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveStringCommands.GetBitCommand>> getBit(
Publisher<ReactiveStringCommands.GetBitCommand> commands);
class SetBitCommand extends KeyCommand {
private Long offset;
private Boolean value;
private SetBitCommand(ByteBuffer key, Long offset, Boolean value) {
super(key);
this.offset = offset;
this.value = value;
}
public static ReactiveStringCommands.SetBitCommand bit(ByteBuffer key) {
return new SetBitCommand(key, null, null);
}
public ReactiveStringCommands.SetBitCommand atOffset(Long index) {
return new ReactiveStringCommands.SetBitCommand(getKey(), index, value);
}
public ReactiveStringCommands.SetBitCommand to(Boolean bit) {
return new ReactiveStringCommands.SetBitCommand(getKey(), offset, bit);
}
public Long getOffset() {
return offset;
}
public Boolean getValue() {
return value;
}
}
/**
* Sets the bit at {@code offset} in value stored at {@code key} and return the original value.
*
* @param key must not be {@literal null}.
* @param offset
* @param value
* @return
*/
default Mono<Boolean> setBit(ByteBuffer key, long offset, boolean value) {
Assert.notNull(key, "Key must not be null!");
return setBit(Mono.just(SetBitCommand.bit(key).atOffset(offset).to(value))).next().map(BooleanResponse::getOutput);
}
/**
* Sets the bit at {@code offset} in value stored at {@code key} and return the original value.
*
* @param keys must not be {@literal null}.
* @param offset must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
*/
Flux<BooleanResponse<ReactiveStringCommands.SetBitCommand>> setBit(
Publisher<ReactiveStringCommands.SetBitCommand> commands);
/**
* @author Christoph Strobl
*/
class BitCountCommand extends KeyCommand {
private Range<Long> range;
public BitCountCommand(ByteBuffer key, Range<Long> range) {
super(key);
this.range = range;
}
public static ReactiveStringCommands.BitCountCommand bitCount(ByteBuffer key) {
return new ReactiveStringCommands.BitCountCommand(key, null);
}
public ReactiveStringCommands.BitCountCommand within(Range<Long> range) {
return new ReactiveStringCommands.BitCountCommand(getKey(), range);
}
public Range<Long> getRange() {
return range;
}
}
/**
* Count the number of set bits (population counting) in value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> bitCount(ByteBuffer key) {
Assert.notNull(key, "Key must not be null");
return bitCount(Mono.just(BitCountCommand.bitCount(key))).next().map(NumericResponse::getOutput);
}
/**
* Count the number of set bits (population counting) of value stored at {@code key} between {@code begin} and
* {@code end}.
*
* @param key must not be {@literal null}.
* @param begin
* @param end
* @return
*/
default Mono<Long> bitCount(ByteBuffer key, long begin, long end) {
Assert.notNull(key, "Key must not be null");
return bitCount(Mono.just(BitCountCommand.bitCount(key).within(new Range<>(begin, end)))).next()
.map(NumericResponse::getOutput);
}
/**
* Count the number of set bits (population counting) of value stored at {@code key} between {@code begin} and
* {@code end}.
*
* @param keys must not be {@literal null}.
* @param begin must not be {@literal null}.
* @param end must not be {@literal null}.
* @return
*/
Flux<NumericResponse<ReactiveStringCommands.BitCountCommand, Long>> bitCount(
Publisher<ReactiveStringCommands.BitCountCommand> commands);
/**
* @author Christoph Strobl
*/
class BitOpCommand {
private List<ByteBuffer> keys;
private BitOperation bitOp;
private ByteBuffer destinationKey;
private BitOpCommand(List<ByteBuffer> keys, BitOperation bitOp, ByteBuffer destinationKey) {
this.keys = keys;
this.bitOp = bitOp;
this.destinationKey = destinationKey;
}
public static ReactiveStringCommands.BitOpCommand perform(BitOperation bitOp) {
return new ReactiveStringCommands.BitOpCommand(null, bitOp, null);
}
public BitOperation getBitOp() {
return bitOp;
}
public ReactiveStringCommands.BitOpCommand onKeys(List<ByteBuffer> keys) {
return new ReactiveStringCommands.BitOpCommand(keys, bitOp, destinationKey);
}
public List<ByteBuffer> getKeys() {
return keys;
}
public ReactiveStringCommands.BitOpCommand andSaveAs(ByteBuffer destinationKey) {
return new ReactiveStringCommands.BitOpCommand(keys, bitOp, destinationKey);
}
public ByteBuffer getDestinationKey() {
return destinationKey;
}
}
/**
* Perform bitwise operations between strings.
*
* @param keys must not be {@literal null}.
* @param bitOp must not be {@literal null}.
* @param destination must not be {@literal null}.
* @return
*/
default Mono<Long> bitOp(List<ByteBuffer> keys, BitOperation bitOp, ByteBuffer destination) {
Assert.notNull(keys, "keys must not be null");
return bitOp(Mono.just(BitOpCommand.perform(bitOp).onKeys(keys).andSaveAs(destination))).next()
.map(NumericResponse::getOutput);
}
/**
* Perform bitwise operations between strings.
*
* @param keys must not be {@literal null}.
* @param bitOp must not be {@literal null}.
* @param destination must not be {@literal null}.
* @return
*/
Flux<NumericResponse<ReactiveStringCommands.BitOpCommand, Long>> bitOp(
Publisher<ReactiveStringCommands.BitOpCommand> commands);
/**
* Get the length of the value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
*/
default Mono<Long> strLen(ByteBuffer key) {
Assert.notNull(key, "key must not be null");
return strLen(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput);
}
/**
* Get the length of the value stored at {@code key}.
*
* @param keys must not be {@literal null}.
* @return
*/
Flux<NumericResponse<KeyCommand, Long>> strLen(Publisher<KeyCommand> keys);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2016 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.
@@ -37,11 +37,23 @@ public interface RedisConnectionFactory extends PersistenceExceptionTranslator {
* Provides a suitable connection for interacting with Redis Cluster.
*
* @return
* @throws
* @since 1.7
*/
RedisClusterConnection getClusterConnection();
/**
* @return
* @since 2.0.
*/
ReactiveRedisConnection getReactiveConnection();
/**
*
* @return
* @since 2.0
*/
ReactiveRedisClusterConnection getReactiveClusterConnection();
/**
* Specifies if pipelined results should be converted to the expected data type. If false, results of
* {@link RedisConnection#closePipeline()} and {RedisConnection#exec()} will be of the type returned by the underlying

View File

@@ -199,7 +199,7 @@ public interface RedisGeoCommands {
* @author Christoph Strobl
* @since 1.8
*/
public class GeoRadiusCommandArgs {
public class GeoRadiusCommandArgs implements Cloneable {
Set<Flag> flags = new LinkedHashSet<Flag>(2, 1);
Long limit;
@@ -309,6 +309,16 @@ public interface RedisGeoCommands {
public static enum Flag {
WITHCOORD, WITHDIST
}
@Override
protected GeoRadiusCommandArgs clone() {
GeoRadiusCommandArgs tmp = new GeoRadiusCommandArgs();
tmp.flags = this.flags != null ? new LinkedHashSet<>(this.flags) : new LinkedHashSet<>(2);
tmp.limit = this.limit;
tmp.sortDirection = this.sortDirection;
return tmp;
}
}
/**

View File

@@ -174,6 +174,10 @@ abstract public class Converters {
};
}
public static Boolean stringToBoolean(String s) {
return ObjectUtils.nullSafeEquals("OK", s);
}
public static Converter<String, Properties> stringToProps() {
return STRING_TO_PROPS;
}
@@ -378,7 +382,7 @@ abstract public class Converters {
* @author Christoph Strobl
* @since 1.8
*/
static enum DistanceConverterFactory {
enum DistanceConverterFactory {
INSTANCE;

View File

@@ -33,14 +33,7 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.ClusterCommandExecutor;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.*;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
@@ -362,8 +355,22 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
/*
* (non-Javadoc)
* @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException)
* @see org.springframework.data.redis.connection.RedisConnectionFactory#getReactiveConnection()
*/
@Override
public ReactiveRedisConnection getReactiveConnection() {
throw new UnsupportedOperationException("Jedis does not support racative connections");
}
@Override
public ReactiveRedisClusterConnection getReactiveClusterConnection() {
throw new UnsupportedOperationException("Jedis does not support racative connections");
}
/*
* (non-Javadoc)
* @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException)
*/
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return EXCEPTION_TRANSLATION.translate(ex);
}

View File

@@ -204,6 +204,24 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
return new LettuceClusterConnection((RedisClusterClient) client, clusterCommandExecutor);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnectionFactory#getReactiveConnection()
*/
@Override
public LettuceReactiveRedisConnection getReactiveConnection() {
return new LettuceReactiveRedisConnection(client);
}
@Override
public LettuceReactiveRedisClusterConnection getReactiveClusterConnection() {
if(!isClusterAware()) {
throw new InvalidDataAccessApiUsageException("Cluster is not configured!");
}
return new LettuceReactiveRedisClusterConnection((RedisClusterClient)client);
}
public void initConnection() {
synchronized (this.connectionMonitor) {

View File

@@ -345,6 +345,10 @@ abstract public class LettuceConverters extends Converters {
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>>() {

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2016. 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 org.springframework.data.redis.connection.ReactiveClusterGeoCommands;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveClusterGeoCommands extends LettuceReactiveGeoCommands
implements ReactiveClusterGeoCommands {
/**
* Create new {@link LettuceReactiveGeoCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterGeoCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2016. 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 org.springframework.data.redis.connection.ReactiveClusterHashCommands;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveClusterHashCommands extends LettuceReactiveHashCommands
implements ReactiveClusterHashCommands {
/**
* Create new {@link LettuceReactiveHashCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterHashCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2016. 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 java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ReactiveClusterHyperLogLogCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since @since 2.0
*/
public class LettuceReactiveClusterHyperLogLogCommands extends LettuceReactiveHyperLogLogCommands
implements ReactiveClusterHyperLogLogCommands {
/**
* Create new {@link LettuceReactiveHyperLogLogCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterHyperLogLogCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
@Override
public Flux<ReactiveRedisConnection.BooleanResponse<PfMergeCommand>> pfMerge(Publisher<PfMergeCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null for PFMERGE");
Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty for PFMERGE!");
List<ByteBuffer> keys = new ArrayList<>(command.getSourceKeys());
keys.add(command.getKey());
if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys.toArray(new ByteBuffer[keys.size()]))) {
return super.pfMerge(Mono.just(command));
}
return Mono
.error(new InvalidDataAccessApiUsageException("All keys must map to same slot for PFMERGE in cluster mode."));
}));
}
@Override
public Flux<ReactiveRedisConnection.NumericResponse<PfCountCommand, Long>> pfCount(
Publisher<PfCountCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notEmpty(command.getKeys(), "Keys must be null or empty for PFCOUNT!");
if (ClusterSlotHashUtil
.isSameSlotForAllKeys(command.getKeys().toArray(new ByteBuffer[command.getKeys().size()]))) {
return super.pfCount(Mono.just(command));
}
return Mono
.error(new InvalidDataAccessApiUsageException("All keys must map to same slot for PFCOUNT in cluster mode."));
}));
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2016. 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 java.nio.ByteBuffer;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ReactiveClusterKeyCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.util.Assert;
import com.lambdaworks.redis.RedisException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Observable;
/**
* @author Christoph Strobl.
* @since 2.0
*/
public class LettuceReactiveClusterKeyCommands extends LettuceReactiveKeyCommands
implements ReactiveClusterKeyCommands {
private LettuceReactiveRedisClusterConnection connection;
/**
* Create new {@link LettuceReactiveKeyCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterKeyCommands(LettuceReactiveRedisClusterConnection connection) {
super(connection);
this.connection = connection;
}
@Override
public Mono<List<ByteBuffer>> keys(RedisClusterNode node, ByteBuffer pattern) {
return connection.execute(node, cmd -> {
Assert.notNull(pattern, "Pattern must not be null!");
Observable<List<ByteBuffer>> result = cmd.keys(pattern.array()).map(ByteBuffer::wrap).toList();
return Flux.from(LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result));
}).next();
}
@Override
public Mono<ByteBuffer> randomKey(RedisClusterNode node) {
return connection.execute(node, cmd -> {
Observable<ByteBuffer> result = cmd.randomkey().map(ByteBuffer::wrap);
return Flux.from(LettuceReactiveRedisConnection.<ByteBuffer> monoConverter().convert(result));
}).next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#rename(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<ReactiveRedisConnection.BooleanResponse<RenameCommand>> rename(Publisher<RenameCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "key must not be null.");
Assert.notNull(command.getNewName(), "NewName must not be null");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKey(), command.getNewName())) {
return super.rename(Mono.just(command));
}
Observable<Boolean> result = cmd.dump(command.getKey().array())
.switchIfEmpty(Observable.error(new RedisSystemException("Cannot rename key that does not exist",
new RedisException("ERR no such key."))))
.concatMap(value -> cmd.restore(command.getNewName().array(), 0, value)
.concatMap(res -> cmd.del(command.getKey().array())))
.map(LettuceConverters.longToBooleanConverter()::convert);
return LettuceReactiveRedisConnection.<Boolean> monoConverter().convert(result)
.map(val -> new ReactiveRedisConnection.BooleanResponse<>(command, val));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#renameNX(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<ReactiveRedisConnection.BooleanResponse<RenameCommand>> renameNX(Publisher<RenameCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null.");
Assert.notNull(command.getNewName(), "NewName must not be null");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKey(), command.getNewName())) {
return super.renameNX(Mono.just(command));
}
Observable<Boolean> result =
cmd.exists(command.getNewName().array()).concatMap(exists -> {
if (exists == 1) {
return Observable.just(Boolean.FALSE);
}
return cmd.dump(command.getKey().array())
.switchIfEmpty(Observable.error(new RedisSystemException("Cannot rename key that does not exist",
new RedisException("ERR no such key."))))
.concatMap(value -> cmd.restore(command.getNewName().array(), 0, value)
.concatMap(res -> cmd.del(command.getKey().array())))
.map(LettuceConverters.longToBooleanConverter()::convert);
});
return LettuceReactiveRedisConnection.<Boolean> monoConverter().convert(result)
.map(val -> new ReactiveRedisConnection.BooleanResponse<>(command, val));
}));
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2016. 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 java.nio.ByteBuffer;
import org.reactivestreams.Publisher;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ReactiveClusterListCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveClusterListCommands extends LettuceReactiveListCommands
implements ReactiveClusterListCommands {
/**
* Create new {@link LettuceReactiveListCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterListCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
@Override
public Flux<PopResponse> bPop(Publisher<BPopCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
Assert.notNull(command.getDirection(), "Direction must not be null!");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKeys())) {
return super.bPop(Mono.just(command));
}
return Mono.error(new InvalidDataAccessApiUsageException("All keys must map to the same slot for BPOP command."));
}));
}
@Override
public Flux<ReactiveRedisConnection.ByteBufferResponse<RPopLPushCommand>> rPopLPush(
Publisher<RPopLPushCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getDestination(), "Destination key must not be null!");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKey(), command.getDestination())) {
return super.rPopLPush(Mono.just(command));
}
Observable<ByteBuffer> result = cmd.rpop(command.getKey().array())
.concatMap(value -> cmd.lpush(command.getDestination().array(), value).map(x -> value)).map(ByteBuffer::wrap);
return LettuceReactiveRedisConnection.<ByteBuffer> monoConverter().convert(result)
.map(value -> new ReactiveRedisConnection.ByteBufferResponse<>(command, value));
}));
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2016. 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 org.springframework.data.redis.connection.ReactiveClusterNumberCommands;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveClusterNumberCommands extends LettuceReactiveNumberCommands
implements ReactiveClusterNumberCommands {
/**
* Create new {@link LettuceReactiveStringCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterNumberCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright 2016. 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 java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ReactiveClusterSetCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveClusterSetCommands extends LettuceReactiveSetCommands
implements ReactiveClusterSetCommands {
/**
* Create new {@link LettuceReactiveSetCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterSetCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
@Override
public Flux<ReactiveRedisConnection.MultiValueResponse<SUnionCommand, ByteBuffer>> sUnion(
Publisher<SUnionCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKeys())) {
return super.sUnion(Mono.just(command));
}
Observable<List<ByteBuffer>> result = Observable
.merge(command.getKeys().stream().map(key -> cmd.smembers(key.array())).collect(Collectors.toList()))
.map(ByteBuffer::wrap).distinct().toList();
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result)
.map(value -> new ReactiveRedisConnection.MultiValueResponse<>(command, value));
}));
}
@Override
public Flux<ReactiveRedisConnection.NumericResponse<SUnionStoreCommand, Long>> sUnionStore(
Publisher<SUnionStoreCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Source keys must not be null!");
Assert.notNull(command.getKey(), "Destination key must not be null!");
List<ByteBuffer> keys = new ArrayList<>(command.getKeys());
keys.add(command.getKey());
if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) {
return super.sUnionStore(Mono.just(command));
}
return sUnion(Mono.just(SUnionCommand.keys(command.getKeys()))).next().flatMap(values -> {
Observable<Long> result = cmd.sadd(command.getKey().array(),
values.getOutput().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]));
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new ReactiveRedisConnection.NumericResponse<>(command, value));
});
}));
}
@Override
public Flux<ReactiveRedisConnection.MultiValueResponse<SInterCommand, ByteBuffer>> sInter(
Publisher<SInterCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKeys())) {
return super.sInter(Mono.just(command));
}
Observable<List<ByteBuffer>> sourceSet = cmd.smembers(command.getKeys().get(0).array()).map(ByteBuffer::wrap)
.distinct().toList();
List<Observable<List<ByteBuffer>>> intersectingSets = new ArrayList<>();
for (int i = 1; i < command.getKeys().size(); i++) {
intersectingSets.add(cmd.smembers(command.getKeys().get(i).array()).map(ByteBuffer::wrap).distinct().toList());
}
Observable<List<ByteBuffer>> result = Observable.zip(sourceSet, Observable.merge(intersectingSets),
(source, intersecting) -> {
source.retainAll(intersecting);
return source;
});
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result)
.map(value -> new ReactiveRedisConnection.MultiValueResponse<>(command, value));
}));
}
@Override
public Flux<ReactiveRedisConnection.NumericResponse<SInterStoreCommand, Long>> sInterStore(
Publisher<SInterStoreCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Source keys must not be null!");
Assert.notNull(command.getKey(), "Destination key must not be null!");
List<ByteBuffer> keys = new ArrayList<>(command.getKeys());
keys.add(command.getKey());
if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) {
return super.sInterStore(Mono.just(command));
}
return sInter(Mono.just(SInterCommand.keys(command.getKeys()))).next().flatMap(values -> {
Observable<Long> result = cmd.sadd(command.getKey().array(),
values.getOutput().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]));
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new ReactiveRedisConnection.NumericResponse<>(command, value));
});
}));
}
@Override
public Flux<ReactiveRedisConnection.MultiValueResponse<SDiffCommand, ByteBuffer>> sDiff(
Publisher<SDiffCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKeys())) {
return super.sDiff(Mono.just(command));
}
Observable<List<ByteBuffer>> sourceSet = cmd.smembers(command.getKeys().get(0).array()).map(ByteBuffer::wrap)
.distinct().toList();
List<Observable<List<ByteBuffer>>> intersectingSets = new ArrayList<>();
for (int i = 1; i < command.getKeys().size(); i++) {
intersectingSets.add(cmd.smembers(command.getKeys().get(i).array()).map(ByteBuffer::wrap).distinct().toList());
}
Observable<List<ByteBuffer>> result = Observable.zip(sourceSet, Observable.merge(intersectingSets),
(source, intersecting) -> {
source.removeAll(intersecting);
return source;
});
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result)
.map(value -> new ReactiveRedisConnection.MultiValueResponse<>(command, value));
}));
}
@Override
public Flux<ReactiveRedisConnection.NumericResponse<SDiffStoreCommand, Long>> sDiffStore(
Publisher<SDiffStoreCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Source keys must not be null!");
Assert.notNull(command.getKey(), "Destination key must not be null!");
List<ByteBuffer> keys = new ArrayList<>(command.getKeys());
keys.add(command.getKey());
if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) {
return super.sDiffStore(Mono.just(command));
}
return sDiff(Mono.just(SDiffCommand.keys(command.getKeys()))).next().flatMap(values -> {
Observable<Long> result = cmd.sadd(command.getKey().array(),
values.getOutput().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]));
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new ReactiveRedisConnection.NumericResponse<>(command, value));
});
}));
}
@Override
public Flux<ReactiveRedisConnection.BooleanResponse<SMoveCommand>> sMove(Publisher<SMoveCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Source key must not be null!");
Assert.notNull(command.getDestination(), "Destination key must not be null!");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKey(), command.getDestination())) {
return super.sMove(Mono.just(command));
}
Observable<Boolean> result = cmd.exists(command.getKey().array()).flatMap(nrKeys -> nrKeys == 0
? Observable.empty() : cmd.sismember(command.getKey().array(), command.getValue().array()))
.flatMap(exists -> {
if (!exists) {
return Observable.just(Boolean.FALSE);
}
return cmd.sismember(command.getDestination().array(), command.getValue().array())
.flatMap(existsInTarget -> {
Observable<Boolean> tmp = cmd.srem(command.getKey().array(), command.getValue().array())
.map(nrRemoved -> nrRemoved > 0);
if (!existsInTarget) {
return tmp.flatMap(removed -> cmd.sadd(command.getDestination().array(), command.getValue().array())
.map(LettuceConverters::toBoolean));
}
return tmp;
});
});
return LettuceReactiveRedisConnection.<Boolean> monoConverter().convert(result.defaultIfEmpty(Boolean.FALSE))
.map(value -> new ReactiveRedisConnection.BooleanResponse<>(command, value));
}));
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2016. 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 java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ReactiveClusterStringCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveClusterStringCommands extends LettuceReactiveStringCommands
implements ReactiveClusterStringCommands {
/**
* Create new {@link LettuceReactiveStringCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterStringCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
@Override
public Flux<ReactiveRedisConnection.NumericResponse<BitOpCommand, Long>> bitOp(Publisher<BitOpCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
List<ByteBuffer> keys = new ArrayList<>(command.getKeys());
keys.add(command.getDestinationKey());
if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) {
return super.bitOp(Mono.just(command));
}
return Mono
.error(new InvalidDataAccessApiUsageException("All keys must map to the same slot for BITOP command."));
}));
}
@Override
public Flux<ReactiveRedisConnection.BooleanResponse<MSetCommand>> mSetNX(Publisher<MSetCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getKeyValuePairs().keySet())) {
return super.mSetNX(Mono.just(command));
}
return Mono
.error(new InvalidDataAccessApiUsageException("All keys must map to the same slot for MSETNX command."));
}));
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2016. 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 org.reactivestreams.Publisher;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ReactiveClusterZSetCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since @since 2.0
*/
public class LettuceReactiveClusterZSetCommands extends LettuceReactiveZSetCommands
implements ReactiveClusterZSetCommands {
/**
* Create new {@link LettuceReactiveSetCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveClusterZSetCommands(LettuceReactiveRedisConnection connection) {
super(connection);
}
@Override
public Flux<ReactiveRedisConnection.NumericResponse<ZUnionStoreCommand, Long>> zUnionStore(
Publisher<ZUnionStoreCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty.");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getSourceKeys())) {
return super.zUnionStore(Mono.just(command));
}
return Mono
.error(new InvalidDataAccessApiUsageException("All keys must map to the same slot for ZUNIONSTORE command."));
}));
}
@Override
public Flux<ReactiveRedisConnection.NumericResponse<ZInterStoreCommand, Long>> zInterStore(
Publisher<ZInterStoreCommand> commands) {
return getConnection().execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty.");
if (ClusterSlotHashUtil.isSameSlotForAllKeys(command.getSourceKeys())) {
return super.zInterStore(Mono.just(command));
}
return Mono
.error(new InvalidDataAccessApiUsageException("All keys must map to the same slot for ZINTERSTORE command."));
}));
}
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.reactivestreams.Publisher;
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.Point;
import org.springframework.data.redis.connection.ReactiveGeoCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.util.Assert;
import com.lambdaworks.redis.GeoArgs;
import com.lambdaworks.redis.GeoWithin;
import reactor.core.publisher.Flux;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveGeoCommands implements ReactiveGeoCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveGeoCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveGeoCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveGeoCommands#geoAdd(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<GeoAddCommand, Long>> geoAdd(Publisher<GeoAddCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getGeoLocations(), "Locations must not be null!");
List<Object> values = new ArrayList<>();
for (GeoLocation<ByteBuffer> location : command.getGeoLocations()) {
Assert.notNull(location.getName(), "Location.Name must not be null!");
Assert.notNull(location.getPoint(), "Location.Point must not be null!");
values.add(location.getPoint().getX());
values.add(location.getPoint().getY());
values.add(location.getName().array());
}
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.geoadd(command.getKey().array(), values.toArray()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveGeoCommands#geoDist(org.reactivestreams.Publisher)
*/
@Override
public Flux<CommandResponse<GeoDistCommand, Distance>> geoDist(Publisher<GeoDistCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getFrom(), "From member must not be null!");
Assert.notNull(command.getTo(), "To member must not be null!");
Metric metric = command.getMetric().isPresent() ? command.getMetric().get() : RedisGeoCommands.DistanceUnit.METERS;
GeoArgs.Unit geoUnit = LettuceConverters.toGeoArgsUnit(metric);
Converter<Double, Distance> distanceConverter = LettuceConverters.distanceConverterForMetric(metric);
Observable<Distance> result = cmd
.geodist(command.getKey().array(), command.getFrom().array(), command.getTo().array(), geoUnit)
.map(distanceConverter::convert);
return LettuceReactiveRedisConnection.<Distance> monoConverter().convert(result)
.map(value -> new CommandResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveGeoCommands#geoHash(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<GeoHashCommand, String>> geoHash(Publisher<GeoHashCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getMembers(), "Members must not be null!");
return LettuceReactiveRedisConnection.<List<String>> monoConverter()
.convert(cmd.geohash(command.getKey().array(),
command.getMembers().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])).toList())
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveGeoCommands#geoPos(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<GeoPosCommand, Point>> geoPos(Publisher<GeoPosCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getMembers(), "Members must not be null!");
Observable<List<Point>> result = cmd
.geopos(command.getKey().array(),
command.getMembers().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]))
.map(LettuceConverters::geoCoordinatesToPoint).toList();
return LettuceReactiveRedisConnection.<List<Point>> monoConverter().convert(result)
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveGeoCommands#geoRadius(org.reactivestreams.Publisher)
*/
@Override
public Flux<CommandResponse<GeoRadiusCommand, GeoResults<GeoLocation<ByteBuffer>>>> geoRadius(
Publisher<GeoRadiusCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getPoint(), "Point must not be null!");
Assert.notNull(command.getDistance(), "Distance must not be null!");
GeoArgs geoArgs = command.getArgs().isPresent() ? LettuceConverters.toGeoArgs(command.getArgs().get()) : new GeoArgs();
Observable<GeoResults<GeoLocation<ByteBuffer>>> result = cmd
.georadius(command.getKey().array(), command.getPoint().getX(), command.getPoint().getY(),
command.getDistance().getValue(), LettuceConverters.toGeoArgsUnit(command.getDistance().getMetric()),
geoArgs)
.map(converter(command.getDistance().getMetric())::convert).toList().map(vals -> new GeoResults<>(vals));
return LettuceReactiveRedisConnection.<GeoResults<GeoLocation<ByteBuffer>>> monoConverter().convert(result)
.map(value -> new CommandResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveGeoCommands#geoRadiusByMember(org.reactivestreams.Publisher)
*/
@Override
public Flux<CommandResponse<GeoRadiusByMemberCommand, GeoResults<GeoLocation<ByteBuffer>>>> geoRadiusByMember(
Publisher<GeoRadiusByMemberCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getMember(), "Member must not be null!");
Assert.notNull(command.getDistance(), "Distance must not be null!");
GeoArgs geoArgs = command.getArgs().isPresent() ? LettuceConverters.toGeoArgs(command.getArgs().get()) : new GeoArgs();
Observable<GeoResults<GeoLocation<ByteBuffer>>> result = cmd
.georadiusbymember(command.getKey().array(), command.getMember().array(), command.getDistance().getValue(),
LettuceConverters.toGeoArgsUnit(command.getDistance().getMetric()), geoArgs)
.map(converter(command.getDistance().getMetric())::convert).toList().map(vals -> new GeoResults<>(vals));
return LettuceReactiveRedisConnection.<GeoResults<GeoLocation<ByteBuffer>>> monoConverter().convert(result)
.map(value -> new CommandResponse<>(command, value));
}));
}
private Converter<GeoWithin<byte[]>, GeoResult<GeoLocation<ByteBuffer>>> converter(Metric metric) {
return (source) -> {
Point point = LettuceConverters.geoCoordinatesToPoint(source.coordinates);
return new GeoResult<>(new GeoLocation<>(ByteBuffer.wrap(source.member), point),
new Distance(source.distance != null ? source.distance : 0D, metric));
};
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveHashCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveHashCommands implements ReactiveHashCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveHashCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveHashCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hSet(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<HSetCommand>> hSet(Publisher<HSetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getFieldValueMap(), "FieldValueMap must not be null!");
Observable<Boolean> result = Observable.empty();
if (command.getFieldValueMap().size() == 1) {
Entry<ByteBuffer, ByteBuffer> entry = command.getFieldValueMap().entrySet().iterator().next();
result = ObjectUtils.nullSafeEquals(command.isUpsert(), Boolean.TRUE)
? cmd.hset(command.getKey().array(), entry.getKey().array(), entry.getValue().array())
: cmd.hsetnx(command.getKey().array(), entry.getKey().array(), entry.getValue().array());
} else {
Map<byte[], byte[]> entries = command.getFieldValueMap().entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().array(), e -> e.getValue().array()));
result = cmd.hmset(command.getKey().array(), entries).map(LettuceConverters::stringToBoolean);
}
return LettuceReactiveRedisConnection.<Boolean> monoConverter().convert(result)
.map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hMGet(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<HGetCommand, ByteBuffer>> hMGet(Publisher<HGetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getFields(), "Fields must not be null!");
Observable<List<ByteBuffer>> result = null;
if (command.getFields().size() == 1) {
result = cmd.hget(command.getKey().array(), command.getFields().iterator().next().array()).map(ByteBuffer::wrap)
.map(val -> val != null ? Collections.singletonList(val) : Collections.emptyList());
} else {
result = cmd
.hmget(command.getKey().array(),
command.getFields().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]))
.map(val -> val != null ? ByteBuffer.wrap(val) : null).toList();
}
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result)
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hExists(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<HExistsCommand>> hExists(Publisher<HExistsCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getName(), "Name must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.hexists(command.getKey().array(), command.getField().array()))
.map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hDel(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<HDelCommand, Long>> hDel(Publisher<HDelCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getFields(), "Fields must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.hdel(command.getKey().array(),
command.getFields().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hLen(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> hLen(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Command.getKey() must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(cmd.hlen(command.getKey().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hKeys(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<KeyCommand, ByteBuffer>> hKeys(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Observable<List<ByteBuffer>> result = cmd.hkeys(command.getKey().array()).map(ByteBuffer::wrap).toList();
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result)
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hKeys(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<KeyCommand, ByteBuffer>> hVals(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Observable<List<ByteBuffer>> result = cmd.hvals(command.getKey().array()).map(ByteBuffer::wrap).toList();
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result)
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hGetAll(org.reactivestreams.Publisher)
*/
@Override
public Flux<CommandResponse<KeyCommand, Map<ByteBuffer, ByteBuffer>>> hGetAll(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Observable<Map<ByteBuffer, ByteBuffer>> result = cmd.hgetall(command.getKey().array()).map(val -> val.entrySet()
.stream().collect(Collectors.toMap(e -> ByteBuffer.wrap(e.getKey()), e -> ByteBuffer.wrap(e.getValue()))));
return LettuceReactiveRedisConnection.<Map<ByteBuffer, ByteBuffer>> monoConverter().convert(result)
.map(value -> new CommandResponse<>(command, value));
}));
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveHyperLogLogCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveHyperLogLogCommands implements ReactiveHyperLogLogCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveHyperLogLogCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveHyperLogLogCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHyperLogLogCommands#pfAdd(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<PfAddCommand, Long>> pfAdd(Publisher<PfAddCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.pfadd(command.getKey().array(),
command.getValues().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHyperLogLogCommands#pfCount(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<PfCountCommand, Long>> pfCount(Publisher<PfCountCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notEmpty(command.getKeys(), "Keys must not be empty for PFCOUNT.");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.pfcount(command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHyperLogLogCommands#pfMerge(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<PfMergeCommand>> pfMerge(Publisher<PfMergeCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Destination key must not be null for PFMERGE.");
Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null for PFMERGE.");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd
.pfmerge(command.getKey().array(),
command.getSourceKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]))
.map(LettuceConverters::stringToBoolean))
.map(value -> new BooleanResponse<>(command, value));
}));
}
protected LettuceReactiveRedisConnection getConnection() {
return connection;
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import java.util.List;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ReactiveKeyCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveKeyCommands implements ReactiveKeyCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveKeyCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveKeyCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#exists(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<KeyCommand>> exists(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap((command) -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<BooleanResponse<KeyCommand>> monoConverter()
.convert(cmd.exists(command.getKey().array()).map(LettuceConverters.longToBooleanConverter()::convert)
.map((value) -> new BooleanResponse<>(command, value)));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#type(org.reactivestreams.Publisher)
*/
@Override
public Flux<CommandResponse<KeyCommand, DataType>> type(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<DataType> monoConverter()
.convert(cmd.type(command.getKey().array()).map(LettuceConverters::toDataType))
.map(respValue -> new CommandResponse<>(command, respValue));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#del(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> del(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap((command) -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<NumericResponse<KeyCommand, Long>> monoConverter()
.convert(cmd.del(command.getKey().array()).map((value) -> new NumericResponse<>(command, value)));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#mDel(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<List<ByteBuffer>, Long>> mDel(Publisher<List<ByteBuffer>> keysCollection) {
return connection.execute(cmd -> Flux.from(keysCollection).flatMap((keys) -> {
Assert.notEmpty(keys, "Keys must not be null!");
return LettuceReactiveRedisConnection.<NumericResponse<List<ByteBuffer>, Long>> monoConverter()
.convert(cmd
.del(keys.stream().map(ByteBuffer::array).collect(Collectors.toList()).toArray(new byte[keys.size()][]))
.map((value) -> new NumericResponse<>(keys, value)));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#keys(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<ByteBuffer, ByteBuffer>> keys(Publisher<ByteBuffer> patterns) {
return connection.execute(cmd -> Flux.from(patterns).flatMap(pattern -> {
Assert.notNull(pattern, "Pattern must not be null!");
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter()
.convert(cmd.keys(pattern.array()).map(ByteBuffer::wrap).toList())
.map(value -> new MultiValueResponse<>(pattern, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#randomKey()
*/
@Override
public Mono<ByteBuffer> randomKey() {
return connection.execute(cmd -> LettuceReactiveRedisConnection.<ByteBuffer> monoConverter()
.convert(cmd.randomkey().map(ByteBuffer::wrap))).next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#rename(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<BooleanResponse<RenameCommand>> rename(Publisher<RenameCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getNewName(), "New name must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter().convert(
cmd.rename(command.getKey().array(), command.getNewName().array()).map(LettuceConverters::stringToBoolean))
.map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#rename(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<BooleanResponse<RenameCommand>> renameNX(Publisher<RenameCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getNewName(), "New name must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.renamenx(command.getKey().array(), command.getNewName().array()))
.map(value -> new BooleanResponse<>(command, value));
}));
}
}

View File

@@ -0,0 +1,315 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ReactiveListCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCommand;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveListCommands implements ReactiveListCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveListCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveListCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lPush(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<PushCommand, Long>> push(Publisher<PushCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notEmpty(command.getValues(), "Values must not be null or empty!");
if (!command.getUpsert() && command.getValues().size() > 1) {
throw new InvalidDataAccessApiUsageException(
String.format("%s PUSHX only allows one value!", command.getDirection()));
}
byte[][] values = command.getValues().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]);
Observable<Long> pushResult = null;
if (ObjectUtils.nullSafeEquals(Direction.RIGHT, command.getDirection())) {
pushResult = command.getUpsert() ? cmd.rpush(command.getKey().array(), values)
: cmd.rpushx(command.getKey().array(), values[0]);
} else {
pushResult = command.getUpsert() ? cmd.lpush(command.getKey().array(), values)
: cmd.lpushx(command.getKey().array(), values[0]);
}
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(pushResult)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lLen(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> lLen(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(cmd.llen(command.getKey().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lRange(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<RangeCommand, ByteBuffer>> lRange(Publisher<RangeCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter()
.convert(cmd
.lrange(command.getKey().array(), command.getRange().getLowerBound(), command.getRange().getUpperBound())
.map(ByteBuffer::wrap).toList())
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lTrim(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<RangeCommand>> lTrim(Publisher<RangeCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd
.ltrim(command.getKey().array(), command.getRange().getLowerBound(), command.getRange().getUpperBound())
.map(LettuceConverters::stringToBoolean))
.map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lIndex(org.reactivestreams.Publisher)
*/
@Override
public Flux<ByteBufferResponse<LIndexCommand>> lIndex(Publisher<LIndexCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getIndex(), "Index value must not be null!");
return LettuceReactiveRedisConnection.<ByteBuffer> monoConverter()
.convert(cmd.lindex(command.getKey().array(), command.getIndex()).map(ByteBuffer::wrap))
.map(value -> new ByteBufferResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lInsert(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<LInsertCommand, Long>> lInsert(Publisher<LInsertCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
Assert.notNull(command.getPivot(), "Pivot must not be null!");
Assert.notNull(command.getPosition(), "Position must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.linsert(command.getKey().array(), Position.BEFORE.equals(command.getPosition()),
command.getPivot().array(), command.getValue().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lSet(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<LSetCommand>> lSet(Publisher<LSetCommand> commands) {
return connection.execute(cmd -> {
return Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "value must not be null!");
Assert.notNull(command.getIndex(), "Index must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.lset(command.getKey().array(), command.getIndex(), command.getValue().array())
.map(LettuceConverters::stringToBoolean))
.map(value -> new BooleanResponse<>(command, value));
});
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lRem(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<LRemCommand, Long>> lRem(Publisher<LRemCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
Assert.notNull(command.getCount(), "Count must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.lrem(command.getKey().array(), command.getCount(), command.getValue().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#rPop(org.reactivestreams.Publisher)
*/
@Override
public Flux<ByteBufferResponse<PopCommand>> pop(Publisher<PopCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getDirection(), "Direction must not be null!");
Observable<byte[]> popResult = ObjectUtils.nullSafeEquals(Direction.RIGHT, command.getDirection())
? cmd.rpop(command.getKey().array()) : cmd.lpop(command.getKey().array());
return LettuceReactiveRedisConnection.<ByteBuffer> monoConverter().convert(popResult.map(ByteBuffer::wrap))
.map(value -> new ByteBufferResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#bPop(org.reactivestreams.Publisher)
*/
@Override
public Flux<PopResponse> bPop(Publisher<BPopCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
Assert.notNull(command.getDirection(), "Direction must not be null!");
byte[][] keys = command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]);
long timeout = command.getTimeout().get(ChronoUnit.SECONDS);
Observable<PopResult> mappedObservable = (ObjectUtils.nullSafeEquals(Direction.RIGHT, command.getDirection())
? cmd.brpop(timeout, keys) : cmd.blpop(timeout, keys))
.map(kv -> Arrays.asList(ByteBuffer.wrap(kv.key), ByteBuffer.wrap(kv.value)))
.map(val -> new PopResult(val));
return LettuceReactiveRedisConnection.<PopResult> monoConverter().convert(mappedObservable)
.map(value -> new PopResponse(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#rPopLPush(org.reactivestreams.Publisher)
*/
@Override
public Flux<ByteBufferResponse<RPopLPushCommand>> rPopLPush(Publisher<RPopLPushCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getDestination(), "Destination key must not be null!");
return LettuceReactiveRedisConnection.<ByteBuffer> monoConverter()
.convert(cmd.rpoplpush(command.getKey().array(), command.getDestination().array()).map(ByteBuffer::wrap))
.map(value -> new ByteBufferResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#bRPopLPush(org.reactivestreams.Publisher)
*/
@Override
public Flux<ByteBufferResponse<BRPopLPushCommand>> bRPopLPush(Publisher<BRPopLPushCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getDestination(), "Destination key must not be null!");
Assert.notNull(command.getTimeout(), "Timeout must not be null!");
return LettuceReactiveRedisConnection.<ByteBuffer> monoConverter()
.convert(cmd.brpoplpush(command.getTimeout().get(ChronoUnit.SECONDS), command.getKey().array(),
command.getDestination().array()).map(ByteBuffer::wrap))
.map(value -> new ByteBufferResponse<>(command, value));
}));
}
protected LettuceReactiveRedisConnection getConnection() {
return connection;
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2016 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 org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveNumberCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
import reactor.core.publisher.Flux;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveNumberCommands implements ReactiveNumberCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveStringCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveNumberCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveNumberCommands#incr(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> incr(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(cmd.incr(command.getKey().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveNumberCommands#incrBy(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public <T extends Number> Flux<NumericResponse<IncrByCommand<T>, T>> incrBy(Publisher<IncrByCommand<T>> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value for INCRBY must not be null.");
T incrBy = command.getValue();
Observable<? extends Number> result = null;
if (incrBy instanceof Double || incrBy instanceof Float) {
result = cmd.incrbyfloat(command.getKey().array(), incrBy.doubleValue());
} else {
result = cmd.incrby(command.getKey().array(), incrBy.longValue());
}
return LettuceReactiveRedisConnection.<T> monoConverter()
.convert(result.map(val -> NumberUtils.convertNumberToTargetClass(val, incrBy.getClass())))
.map(res -> new NumericResponse<>(command, res));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveNumberCommands#decr(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> decr(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(cmd.decr(command.getKey().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveNumberCommands#decrBy(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public <T extends Number> Flux<NumericResponse<DecrByCommand<T>, T>> decrBy(Publisher<DecrByCommand<T>> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value for DECRBY must not be null.");
T decrBy = command.getValue();
Observable<? extends Number> result = null;
if (decrBy instanceof Double || decrBy instanceof Float) {
result = cmd.incrbyfloat(command.getKey().array(), decrBy.doubleValue() * (-1.0D));
} else {
result = cmd.decrby(command.getKey().array(), decrBy.longValue());
}
return LettuceReactiveRedisConnection.<T> monoConverter()
.convert(result.map(val -> NumberUtils.convertNumberToTargetClass(val, decrBy.getClass())))
.map(res -> new NumericResponse<>(command, res));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveNumberCommands#hIncrBy(org.reactivestreams.Publisher)
*/
@Override
public <T extends Number> Flux<NumericResponse<HIncrByCommand<T>, T>> hIncrBy(Publisher<HIncrByCommand<T>> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
T incrBy = command.getValue();
Observable<? extends Number> result = null;
if (incrBy instanceof Double || incrBy instanceof Float) {
result = cmd.hincrbyfloat(command.getKey().array(), command.getField().array(), incrBy.doubleValue());
} else {
result = cmd.hincrby(command.getKey().array(), command.getField().array(), incrBy.longValue());
}
return LettuceReactiveRedisConnection.<T> monoConverter()
.convert(result.map(val -> NumberUtils.convertNumberToTargetClass(val, incrBy.getClass())))
.map(value -> new NumericResponse<>(command, value));
}));
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2016. 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 org.springframework.data.redis.connection.ReactiveRedisClusterConnection;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.lambdaworks.redis.api.rx.RedisReactiveCommands;
import com.lambdaworks.redis.cluster.RedisClusterClient;
import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection;
import com.lambdaworks.redis.cluster.api.rx.RedisClusterReactiveCommands;
import reactor.core.publisher.Flux;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveRedisClusterConnection extends LettuceReactiveRedisConnection
implements ReactiveRedisClusterConnection {
public LettuceReactiveRedisClusterConnection(RedisClusterClient client) {
super(client);
}
@Override
public LettuceReactiveClusterKeyCommands keyCommands() {
return new LettuceReactiveClusterKeyCommands(this);
}
@Override
public LettuceReactiveClusterListCommands listCommands() {
return new LettuceReactiveClusterListCommands(this);
}
@Override
public LettuceReactiveClusterSetCommands setCommands() {
return new LettuceReactiveClusterSetCommands(this);
}
@Override
public LettuceReactiveClusterZSetCommands zSetCommands() {
return new LettuceReactiveClusterZSetCommands(this);
}
@Override
public LettuceReactiveClusterHyperLogLogCommands hyperLogLogCommands() {
return new LettuceReactiveClusterHyperLogLogCommands(this);
}
@Override
public LettuceReactiveClusterStringCommands stringCommands() {
return new LettuceReactiveClusterStringCommands(this);
}
@Override
public LettuceReactiveClusterGeoCommands geoCommands() {
return new LettuceReactiveClusterGeoCommands(this);
}
@Override
public LettuceReactiveClusterHashCommands hashCommands() {
return new LettuceReactiveClusterHashCommands(this);
}
@Override
public LettuceReactiveClusterNumberCommands numberCommands() {
return new LettuceReactiveClusterNumberCommands(this);
}
/**
* @param callback
* @return
*/
public <T> Flux<T> execute(RedisNode node, LettuceReactiveCallback<T> callback) {
try {
Assert.notNull(callback, "ReactiveCallback must not be null!");
Assert.notNull(node, "Node must not be null!");
} catch (IllegalArgumentException e) {
return Flux.error(e);
}
return Flux.defer(() -> callback.doWithCommands(getCommands(node))).onErrorResumeWith(translateExeception());
}
@Override
protected StatefulRedisClusterConnection<byte[], byte[]> getConnection() {
Assert.isInstanceOf(StatefulRedisClusterConnection.class, super.getConnection(),
"Connection needs to be instance of StatefulRedisClusterConnection");
return (StatefulRedisClusterConnection) super.getConnection();
}
protected RedisClusterReactiveCommands<byte[], byte[]> getCommands() {
return getConnection().reactive();
}
protected RedisReactiveCommands<byte[], byte[]> getCommands(RedisNode node) {
if (!(getConnection() instanceof StatefulRedisClusterConnection)) {
throw new IllegalArgumentException("o.O connection needs to be cluster compatible " + getConnection());
}
if (StringUtils.hasText(node.getId())) {
return ((StatefulRedisClusterConnection) getConnection()).getConnection(node.getId()).reactive();
}
return ((StatefulRedisClusterConnection) getConnection()).getConnection(node.getHost(), node.getPort()).reactive();
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2016 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 java.util.function.Function;
import org.reactivestreams.Publisher;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.redis.connection.*;
import org.springframework.util.Assert;
import com.lambdaworks.redis.AbstractRedisClient;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.api.StatefulConnection;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.cluster.RedisClusterClient;
import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection;
import com.lambdaworks.redis.cluster.api.rx.RedisClusterReactiveCommands;
import com.lambdaworks.redis.codec.ByteArrayCodec;
import com.lambdaworks.redis.codec.RedisCodec;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveRedisConnection implements ReactiveRedisConnection {
private StatefulConnection<ByteBuffer, ByteBuffer> connection;
private static final RedisCodec<byte[], byte[]> CODEC = new ByteArrayCodec();
public LettuceReactiveRedisConnection(AbstractRedisClient client) {
Assert.notNull(client, "RedisClient must not be null!");
if (client instanceof RedisClient) {
connection = ((RedisClient) client).connect(CODEC);
} else if (client instanceof RedisClusterClient) {
connection = ((RedisClusterClient) client).connect(CODEC);
} else {
throw new InvalidDataAccessResourceUsageException(
String.format("Cannot use client of type %s", client.getClass()));
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection#keyCommands()
*/
@Override
public ReactiveKeyCommands keyCommands() {
return new LettuceReactiveKeyCommands(this);
}
@Override
public ReactiveStringCommands stringCommands() {
return new LettuceReactiveStringCommands(this);
}
@Override
public ReactiveNumberCommands numberCommands() {
return new LettuceReactiveNumberCommands(this);
}
@Override
public ReactiveListCommands listCommands() {
return new LettuceReactiveListCommands(this);
}
@Override
public ReactiveSetCommands setCommands() {
return new LettuceReactiveSetCommands(this);
}
@Override
public ReactiveZSetCommands zSetCommands() {
return new LettuceReactiveZSetCommands(this);
}
@Override
public ReactiveHashCommands hashCommands() {
return new LettuceReactiveHashCommands(this);
}
@Override
public ReactiveGeoCommands geoCommands() {
return new LettuceReactiveGeoCommands(this);
}
@Override
public ReactiveHyperLogLogCommands hyperLogLogCommands() {
return new LettuceReactiveHyperLogLogCommands(this);
}
/**
* @param callback
* @return
*/
public <T> Flux<T> execute(LettuceReactiveCallback<T> callback) {
return Flux.defer(() -> callback.doWithCommands(getCommands())).onErrorResumeWith(translateExeception());
}
@Override
public void close() {
connection.close();
}
protected StatefulConnection<byte[], byte[]> getConnection() {
return connection;
}
protected RedisClusterReactiveCommands<byte[], byte[]> getCommands() {
if (connection instanceof StatefulRedisConnection) {
return ((StatefulRedisConnection<byte[], byte[]>) connection).reactive();
} else if (connection instanceof StatefulRedisClusterConnection) {
return ((StatefulRedisClusterConnection<byte[], byte[]>) connection).reactive();
}
throw new RuntimeException("o.O unknown connection type " + connection);
}
<T> Function<Throwable, Publisher<? extends T>> translateExeception() {
return throwable -> {
if (throwable instanceof RuntimeException) {
DataAccessException convertedException = null;
if (throwable instanceof RuntimeException) {
convertedException = LettuceConverters.exceptionConverter().convert((RuntimeException) throwable);
}
return Flux.error(convertedException != null ? convertedException : throwable);
}
return Flux.error(throwable);
};
}
interface LettuceReactiveCallback<T> {
Publisher<T> doWithCommands(RedisClusterReactiveCommands<byte[], byte[]> cmd);
}
}

View File

@@ -0,0 +1,316 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.ReactiveSetCommands;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveSetCommands implements ReactiveSetCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveSetCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveSetCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sAdd(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<SAddCommand, Long>> sAdd(Publisher<SAddCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValues(), "Values must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.sadd(command.getKey().array(),
command.getValues().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sRem(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<SRemCommand, Long>> sRem(Publisher<SRemCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValues(), "Values must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.srem(command.getKey().array(),
command.getValues().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sPop(org.reactivestreams.Publisher)
*/
@Override
public Flux<ByteBufferResponse<KeyCommand>> sPop(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<ByteBuffer> monoConverter()
.convert(cmd.spop(command.getKey().array()).map(ByteBuffer::wrap))
.map(value -> new ByteBufferResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sMove(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<SMoveCommand>> sMove(Publisher<SMoveCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getDestination(), "Destination key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.smove(command.getKey().array(), command.getDestination().array(), command.getValue().array()))
.map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sCard(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> sCard(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(cmd.scard(command.getKey().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sIsMember(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<SIsMemberCommand>> sIsMember(Publisher<SIsMemberCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.sismember(command.getKey().array(), command.getValue().array()))
.map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sInter(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<SInterCommand, ByteBuffer>> sInter(Publisher<SInterCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter()
.convert(cmd.sinter(command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]))
.map(ByteBuffer::wrap).toList())
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sInterStore(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<SInterStoreCommand, Long>> sInterStore(Publisher<SInterStoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
Assert.notNull(command.getKey(), "Destination key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.sinterstore(command.getKey().array(),
command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sInter(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<SUnionCommand, ByteBuffer>> sUnion(Publisher<SUnionCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter()
.convert(cmd.sunion(command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]))
.map(ByteBuffer::wrap).toList())
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sInterStore(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<SUnionStoreCommand, Long>> sUnionStore(Publisher<SUnionStoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
Assert.notNull(command.getKey(), "Destination key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.sunionstore(command.getKey().array(),
command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sInter(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<SDiffCommand, ByteBuffer>> sDiff(Publisher<SDiffCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter()
.convert(cmd.sdiff(command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]))
.map(ByteBuffer::wrap).toList())
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sInterStore(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<SDiffStoreCommand, Long>> sDiffStore(Publisher<SDiffStoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKeys(), "Keys must not be null!");
Assert.notNull(command.getKey(), "Destination key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.sdiffstore(command.getKey().array(),
command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sMembers(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<KeyCommand, ByteBuffer>> sMembers(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter()
.convert(cmd.smembers(command.getKey().array()).map(ByteBuffer::wrap).toList())
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sRandMembers(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<SRandMembersCommand, ByteBuffer>> sRandMember(
Publisher<SRandMembersCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
boolean singleElement = !command.getCount().isPresent() || command.getCount().get().equals(1L);
Observable<List<ByteBuffer>> result = singleElement
? cmd.srandmember(command.getKey().array()).map(ByteBuffer::wrap).map(Collections::singletonList)
: cmd.srandmember(command.getKey().array(), command.getCount().get()).map(ByteBuffer::wrap).toList();
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter().convert(result)
.map(value -> new MultiValueResponse<>(command, value));
}));
}
protected LettuceReactiveRedisConnection getConnection() {
return connection;
}
}

View File

@@ -0,0 +1,415 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCommand;
import org.springframework.data.redis.connection.ReactiveStringCommands;
import org.springframework.util.Assert;
import com.lambdaworks.redis.SetArgs;
import reactor.core.publisher.Flux;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveStringCommands implements ReactiveStringCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveStringCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveStringCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#mGet(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<List<ByteBuffer>, ByteBuffer>> mGet(Publisher<List<ByteBuffer>> keyCollections) {
return connection.execute(cmd -> Flux.from(keyCollections).flatMap((keys) -> {
Assert.notNull(keys, "Keys must not be null!");
return LettuceReactiveRedisConnection.<MultiValueResponse<List<ByteBuffer>, ByteBuffer>> monoConverter()
.convert(cmd
.mget(keys.stream().map(ByteBuffer::array).collect(Collectors.toList()).toArray(new byte[keys.size()][]))
.map((value) -> value != null ? ByteBuffer.wrap(value) : ByteBuffer.allocate(0)).toList()
.map((values) -> new MultiValueResponse<>(keys, values)));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#set(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<SetCommand>> set(Publisher<SetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap((command) -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
SetArgs args = null;
if (command.getExpiration().isPresent() || command.getOption().isPresent()) {
args = LettuceConverters.toSetArgs(command.getExpiration().isPresent() ? command.getExpiration().get() : null,
command.getOption().isPresent() ? command.getOption().get() : null);
}
return LettuceReactiveRedisConnection.<Boolean> monoConverter().convert(
args != null ? cmd.set(command.getKey().array(), command.getValue().array(), args)
: cmd.set(command.getKey().array(), command.getValue().array()).map(LettuceConverters::stringToBoolean))
.map((value) -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#getSet(org.reactivestreams.Publisher)
*/
@Override
public Flux<ByteBufferResponse<SetCommand>> getSet(Publisher<SetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap((command) -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
if (command.getExpiration().isPresent() || command.getOption().isPresent()) {
throw new IllegalArgumentException("Command must not define exipiration nor option for GETSET.");
}
return LettuceReactiveRedisConnection.<ByteBufferResponse<SetCommand>> monoConverter()
.convert(cmd.getset(command.getKey().array(), command.getValue().array())
.map((value) -> new ByteBufferResponse<>(command, ByteBuffer.wrap(value)))
.defaultIfEmpty(new ByteBufferResponse<>(command, ByteBuffer.allocate(0))));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#get(org.reactivestreams.Publisher)
*/
@Override
public Flux<ByteBufferResponse<KeyCommand>> get(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap((command) -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<ByteBuffer> monoConverter()
.convert(cmd.get(command.getKey().array()).map(ByteBuffer::wrap))
.map((value) -> new ByteBufferResponse<>(command, value))
.defaultIfEmpty(new ByteBufferResponse<>(command, ByteBuffer.allocate(0)));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#setNX(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<SetCommand>> setNX(Publisher<SetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.setnx(command.getKey().array(), command.getValue().array()))
.map((value) -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#setEX(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<BooleanResponse<SetCommand>> setEX(Publisher<SetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
Assert.isTrue(command.getExpiration().isPresent(), "Expiration time must not be null!");
return LettuceReactiveRedisConnection.<String> monoConverter()
.convert(cmd.setex(command.getKey().array(), command.getExpiration().get().getExpirationTimeInSeconds(),
command.getValue().array()))
.map(LettuceConverters::stringToBoolean).map((value) -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#pSetEX(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<BooleanResponse<SetCommand>> pSetEX(Publisher<SetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
Assert.isTrue(command.getExpiration().isPresent(), "Expiration time must not be null!");
return LettuceReactiveRedisConnection.<String> monoConverter()
.convert(cmd.psetex(command.getKey().array(), command.getExpiration().get().getExpirationTimeInMilliseconds(),
command.getValue().array()))
.map(LettuceConverters::stringToBoolean).map((value) -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#mSet(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<MSetCommand>> mSet(Publisher<MSetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notEmpty(command.getKeyValuePairs(), "Pairs must not be null or empty!");
Map<byte[], byte[]> map = new LinkedHashMap<>();
command.getKeyValuePairs().entrySet().forEach(entry -> map.put(entry.getKey().array(), entry.getValue().array()));
return LettuceReactiveRedisConnection.<String> monoConverter().convert(cmd.mset(map))
.map(LettuceConverters::stringToBoolean).map((value) -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#mSet(org.reactivestreams.Publisher)
*/
@Override
public Flux<BooleanResponse<MSetCommand>> mSetNX(Publisher<MSetCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notEmpty(command.getKeyValuePairs(), "Pairs must not be null or empty!");
Map<byte[], byte[]> map = new LinkedHashMap<>();
command.getKeyValuePairs().entrySet().forEach(entry -> map.put(entry.getKey().array(), entry.getValue().array()));
return LettuceReactiveRedisConnection.<Boolean> monoConverter().convert(cmd.msetnx(map))
.map((value) -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#append(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<AppendCommand, Long>> append(Publisher<AppendCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.append(command.getKey().array(), command.getValue().array()))
.map((value) -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#getRange(org.reactivestreams.Publisher, java.util.function.Supplier, java.util.function.Supplier)
*/
@Override
public Flux<ByteBufferResponse<RangeCommand>> getRange(Publisher<RangeCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
Range<Long> range = command.getRange();
return LettuceReactiveRedisConnection
.<ByteBuffer> monoConverter().convert(cmd
.getrange(command.getKey().array(), range.getLowerBound(), range.getUpperBound()).map(ByteBuffer::wrap))
.map((value) -> new ByteBufferResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#setRange(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<NumericResponse<SetRangeCommand, Long>> setRange(Publisher<SetRangeCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
Assert.notNull(command.getOffset(), "Offset must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.setrange(command.getKey().array(), command.getOffset(), command.getValue().array()))
.map((value) -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#getBit(org.reactivestreams.Publisher, java.util.function.Supplier)
*/
@Override
public Flux<BooleanResponse<GetBitCommand>> getBit(Publisher<GetBitCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getOffset(), "Offset must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.getbit(command.getKey().array(), command.getOffset()).map(LettuceConverters::toBoolean))
.map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#setBit(org.reactivestreams.Publisher, java.util.function.Supplier, java.util.function.Supplier)
*/
@Override
public Flux<BooleanResponse<SetBitCommand>> setBit(Publisher<SetBitCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
Assert.notNull(command.getOffset(), "Offset must not be null!");
return LettuceReactiveRedisConnection.<Boolean> monoConverter()
.convert(cmd.setbit(command.getKey().array(), command.getOffset(), command.getValue() ? 1 : 0)
.map(LettuceConverters::toBoolean))
.map(respValue -> new BooleanResponse<>(command, respValue));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#bitCount(org.reactivestreams.Publisher, java.util.function.Supplier, java.util.function.Supplier)
*/
@Override
public Flux<NumericResponse<BitCountCommand, Long>> bitCount(Publisher<BitCountCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Range<Long> range = command.getRange();
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(range != null ? cmd.bitcount(command.getKey().array(), range.getLowerBound(), range.getUpperBound())
: cmd.bitcount(command.getKey().array()))
.map(responseValue -> new NumericResponse<>(command, responseValue));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#bitOp(org.reactivestreams.Publisher, java.util.function.Supplier, java.util.function.Supplier)
*/
@Override
public Flux<NumericResponse<BitOpCommand, Long>> bitOp(Publisher<BitOpCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getDestinationKey(), "DestinationKey must not be null!");
Assert.notEmpty(command.getKeys(), "Keys must not be null or empty");
Observable<Long> result = null;
byte[] destinationKey = command.getDestinationKey().array();
byte[][] sourceKeys = command.getKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]);
switch (command.getBitOp()) {
case AND:
result = cmd.bitopAnd(destinationKey, sourceKeys);
break;
case OR:
result = cmd.bitopOr(destinationKey, sourceKeys);
break;
case XOR:
result = cmd.bitopXor(destinationKey, sourceKeys);
break;
case NOT:
Assert.isTrue(sourceKeys.length == 1, "BITOP NOT does not allow more than 1 source key.");
result = cmd.bitopNot(destinationKey, sourceKeys[0]);
break;
default:
throw new IllegalArgumentException(String.format("Unknown BITOP '%s'.", command.getBitOp()));
}
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#strLen(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> strLen(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> {
return Flux.from(commands).flatMap(command -> {
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(cmd.strlen(command.getKey().array()))
.map(respValue -> new NumericResponse<>(command, respValue));
});
});
}
protected LettuceReactiveRedisConnection getConnection() {
return connection;
}
}

View File

@@ -0,0 +1,542 @@
/*
* Copyright 2016 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 java.nio.ByteBuffer;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.redis.connection.DefaultTuple;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.ReactiveZSetCommands;
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.lambdaworks.redis.ScoredValue;
import com.lambdaworks.redis.ZAddArgs;
import com.lambdaworks.redis.ZStoreArgs;
import reactor.core.publisher.Flux;
import rx.Observable;
/**
* @author Christoph Strobl
* @since 2.0
*/
public class LettuceReactiveZSetCommands implements ReactiveZSetCommands {
private final LettuceReactiveRedisConnection connection;
/**
* Create new {@link LettuceReactiveSetCommands}.
*
* @param connection must not be {@literal null}.
*/
public LettuceReactiveZSetCommands(LettuceReactiveRedisConnection connection) {
Assert.notNull(connection, "Connection must not be null!");
this.connection = connection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zAdd(org.reactivestreams.Publisher)
*/
@Override
@SuppressWarnings("unchecked")
public Flux<NumericResponse<ZAddCommand, Number>> zAdd(Publisher<ZAddCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notEmpty(command.getTuples(), "Tuples must not be empty or null.");
ZAddArgs args = null;
if (command.getIncr().isPresent() || command.getUpsert().isPresent() || command.getReturnTotalChanged().isPresent()) {
if (command.getIncr().isPresent() && ObjectUtils.nullSafeEquals(command.getIncr().get(), Boolean.TRUE)) {
if (command.getTuples().size() > 1) {
throw new IllegalArgumentException("ZADD INCR must not contain more than one tuple.");
}
Tuple tuple = command.getTuples().iterator().next();
return LettuceReactiveRedisConnection.<Double> monoConverter()
.convert(cmd.zaddincr(command.getKey().array(), tuple.getScore(), tuple.getValue()))
.map(value -> new NumericResponse<>(command, value));
}
if (command.getReturnTotalChanged().isPresent() && ObjectUtils.nullSafeEquals(command.getReturnTotalChanged().get(), Boolean.TRUE)) {
args = ZAddArgs.Builder.ch();
}
if (command.getUpsert().isPresent()) {
if (command.getUpsert().get().equals(Boolean.TRUE)) {
args = ZAddArgs.Builder.nx();
} else {
args = ZAddArgs.Builder.xx();
}
}
}
ScoredValue<byte[]>[] values = (ScoredValue<byte[]>[]) command.getTuples().stream()
.map(tuple -> new ScoredValue<byte[]>(tuple.getScore(), tuple.getValue()))
.toArray(size -> new ScoredValue[size]);
Observable<Long> result = args == null ? cmd.zadd(command.getKey().array(), values)
: cmd.zadd(command.getKey().array(), args, values);
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRem(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZRemCommand, Long>> zRem(Publisher<ZRemCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notEmpty(command.getValues(), "Values must not be null or empty!");
return LettuceReactiveRedisConnection.<Long> monoConverter()
.convert(cmd.zrem(command.getKey().array(),
command.getValues().stream().map(ByteBuffer::array).toArray(size -> new byte[size][])))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zIncrBy(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZIncrByCommand, Double>> zIncrBy(Publisher<ZIncrByCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Member must not be null!");
Assert.notNull(command.getIncrement(), "Increment value must not be null!");
return LettuceReactiveRedisConnection.<Double> monoConverter()
.convert(
cmd.zincrby(command.getKey().array(), command.getIncrement().doubleValue(), command.getValue().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRank(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZRankCommand, Long>> zRank(Publisher<ZRankCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
Observable<Long> result = ObjectUtils.nullSafeEquals(command.getDirection(), Direction.ASC)
? cmd.zrank(command.getKey().array(), command.getValue().array())
: cmd.zrevrank(command.getKey().array(), command.getValue().array());
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRange(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<ZRangeCommand, Tuple>> zRange(Publisher<ZRangeCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
Observable<List<Tuple>> result = Observable.empty();
if (ObjectUtils.nullSafeEquals(command.getDirection(), Direction.ASC)) {
if (command.getWithScores().isPresent() && ObjectUtils.nullSafeEquals(command.getWithScores().get(), Boolean.TRUE)) {
result = cmd.zrangeWithScores(command.getKey().array(), command.getRange().getLowerBound(),
command.getRange().getUpperBound()).map(sc -> (Tuple) new DefaultTuple(sc.value, sc.score)).toList();
} else {
result = cmd
.zrange(command.getKey().array(), command.getRange().getLowerBound(), command.getRange().getUpperBound())
.map(value -> (Tuple) new DefaultTuple(value, Double.NaN)).toList();
}
} else {
if (command.getWithScores().isPresent() && ObjectUtils.nullSafeEquals(command.getWithScores().get(), Boolean.TRUE)) {
result = cmd.zrevrangeWithScores(command.getKey().array(), command.getRange().getLowerBound(),
command.getRange().getUpperBound()).map(sc -> (Tuple) new DefaultTuple(sc.value, sc.score)).toList();
} else {
result = cmd
.zrevrange(command.getKey().array(), command.getRange().getLowerBound(),
command.getRange().getUpperBound())
.map(value -> (Tuple) new DefaultTuple(value, Double.NaN)).toList();
}
}
return LettuceReactiveRedisConnection.<List<Tuple>> monoConverter().convert(result)
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRange(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<ZRangeByScoreCommand, Tuple>> zRangeByScore(Publisher<ZRangeByScoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
Object lowerBound = AgrumentConverters.lowerBoundArgOf(command.getRange());
Object upperBound = AgrumentConverters.upperBoundArgOf(command.getRange());
boolean requiresStringConversion = lowerBound instanceof String || upperBound instanceof String;
boolean isLimited = command.getLimit().isPresent();
Observable<List<Tuple>> result = Observable.empty();
if (ObjectUtils.nullSafeEquals(command.getDirection(), Direction.ASC)) {
if (command.getWithScores().isPresent() && ObjectUtils.nullSafeEquals(command.getWithScores().get(), Boolean.TRUE)) {
if (!isLimited) {
result = (requiresStringConversion
? cmd.zrangebyscoreWithScores(command.getKey().array(), lowerBound.toString(), upperBound.toString())
: cmd.zrangebyscoreWithScores(command.getKey().array(), (Double) lowerBound, (Double) upperBound))
.map(sc -> (Tuple) new DefaultTuple(sc.value, sc.score)).toList();
} else {
result = (requiresStringConversion
? cmd.zrangebyscoreWithScores(command.getKey().array(), lowerBound.toString(), upperBound.toString(),
command.getLimit().get().getOffset(), command.getLimit().get().getCount())
: cmd.zrangebyscoreWithScores(command.getKey().array(), (Double) lowerBound, (Double) upperBound,
command.getLimit().get().getOffset(), command.getLimit().get().getCount()))
.map(sc -> (Tuple) new DefaultTuple(sc.value, sc.score)).toList();
}
} else {
if (!isLimited) {
result = (requiresStringConversion
? cmd.zrangebyscore(command.getKey().array(), lowerBound.toString(), upperBound.toString())
: cmd.zrangebyscore(command.getKey().array(), (Double) lowerBound, (Double) upperBound))
.map(value -> (Tuple) new DefaultTuple(value, Double.NaN)).toList();
} else {
result = (requiresStringConversion
? cmd.zrangebyscore(command.getKey().array(), lowerBound.toString(), upperBound.toString(),
command.getLimit().get().getOffset(), command.getLimit().get().getCount())
: cmd.zrangebyscore(command.getKey().array(), (Double) lowerBound, (Double) upperBound,
command.getLimit().get().getOffset(), command.getLimit().get().getCount()))
.map(value -> (Tuple) new DefaultTuple(value, Double.NaN)).toList();
}
}
} else {
if (command.getWithScores().isPresent() && ObjectUtils.nullSafeEquals(command.getWithScores().get(), Boolean.TRUE)) {
if (!isLimited) {
result = (requiresStringConversion
? cmd.zrevrangebyscoreWithScores(command.getKey().array(), lowerBound.toString(), upperBound.toString())
: cmd.zrevrangebyscoreWithScores(command.getKey().array(), (Double) lowerBound, (Double) upperBound))
.map(sc -> (Tuple) new DefaultTuple(sc.value, sc.score)).toList();
} else {
result = (requiresStringConversion
? cmd.zrevrangebyscoreWithScores(command.getKey().array(), lowerBound.toString(), upperBound.toString(),
command.getLimit().get().getOffset(), command.getLimit().get().getCount())
: cmd.zrevrangebyscoreWithScores(command.getKey().array(), (Double) lowerBound, (Double) upperBound,
command.getLimit().get().getOffset(), command.getLimit().get().getCount()))
.map(sc -> (Tuple) new DefaultTuple(sc.value, sc.score)).toList();
}
} else {
if (!isLimited) {
result = (requiresStringConversion
? cmd.zrevrangebyscore(command.getKey().array(), lowerBound.toString(), upperBound.toString())
: cmd.zrevrangebyscore(command.getKey().array(), (Double) lowerBound, (Double) upperBound))
.map(value -> (Tuple) new DefaultTuple(value, Double.NaN)).toList();
} else {
result = (requiresStringConversion
? cmd.zrevrangebyscore(command.getKey().array(), lowerBound.toString(), upperBound.toString(),
command.getLimit().get().getOffset(), command.getLimit().get().getCount())
: cmd.zrevrangebyscore(command.getKey().array(), (Double) lowerBound, (Double) upperBound,
command.getLimit().get().getOffset(), command.getLimit().get().getCount()))
.map(value -> (Tuple) new DefaultTuple(value, Double.NaN)).toList();
}
}
}
return LettuceReactiveRedisConnection.<List<Tuple>> monoConverter().convert(result)
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zCount(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZCountCommand, Long>> zCount(Publisher<ZCountCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
Object lowerBound = AgrumentConverters.lowerBoundArgOf(command.getRange());
Object upperBound = AgrumentConverters.upperBoundArgOf(command.getRange());
Observable<Long> result = Observable.empty();
if (lowerBound instanceof String || upperBound instanceof String) {
result = cmd.zcount(command.getKey().array(), lowerBound.toString(), upperBound.toString());
} else {
result = cmd.zcount(command.getKey().array(), (Double) lowerBound, (Double) upperBound);
}
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zCard(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<KeyCommand, Long>> zCard(Publisher<KeyCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(cmd.zcard(command.getKey().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zScore(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZScoreCommand, Double>> zScore(Publisher<ZScoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValue(), "Value must not be null!");
return LettuceReactiveRedisConnection.<Double> monoConverter()
.convert(cmd.zscore(command.getKey().array(), command.getValue().array()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRemRangeByRank(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZRemRangeByRankCommand, Long>> zRemRangeByRank(
Publisher<ZRemRangeByRankCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
return LettuceReactiveRedisConnection
.<Long> monoConverter().convert(cmd.zremrangebyrank(command.getKey().array(),
command.getRange().getLowerBound(), command.getRange().getUpperBound()))
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRemRangeByRank(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZRemRangeByScoreCommand, Long>> zRemRangeByScore(
Publisher<ZRemRangeByScoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getRange(), "Range must not be null!");
Object lowerBound = AgrumentConverters.lowerBoundArgOf(command.getRange());
Object upperBound = AgrumentConverters.upperBoundArgOf(command.getRange());
Observable<Long> result = (lowerBound instanceof String || upperBound instanceof String)
? cmd.zremrangebyscore(command.getKey().array(), lowerBound.toString(), upperBound.toString())
: cmd.zremrangebyscore(command.getKey().array(), (Double) lowerBound, (Double) upperBound);
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zUnionStore(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZUnionStoreCommand, Long>> zUnionStore(Publisher<ZUnionStoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Destination key must not be null!");
Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!");
ZStoreArgs args = null;
if (command.getAggregateFunction().isPresent() || !command.getWeights().isEmpty()) {
args = zStoreArgs(command.getAggregateFunction().isPresent() ? command.getAggregateFunction().get() : null, command.getWeights());
}
byte[][] sourceKeys = command.getSourceKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]);
Observable<Long> result = args != null ? cmd.zunionstore(command.getKey().array(), args, sourceKeys)
: cmd.zunionstore(command.getKey().array(), sourceKeys);
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zInterStore(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<ZInterStoreCommand, Long>> zInterStore(Publisher<ZInterStoreCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Destination key must not be null!");
Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!");
ZStoreArgs args = null;
if (command.getAggregateFunction().isPresent() || !command.getWeights().isEmpty()) {
args = zStoreArgs(command.getAggregateFunction().isPresent() ? command.getAggregateFunction().get() : null, command.getWeights());
}
byte[][] sourceKeys = command.getSourceKeys().stream().map(ByteBuffer::array).toArray(size -> new byte[size][]);
Observable<Long> result = args != null ? cmd.zinterstore(command.getKey().array(), args, sourceKeys)
: cmd.zinterstore(command.getKey().array(), sourceKeys);
return LettuceReactiveRedisConnection.<Long> monoConverter().convert(result)
.map(value -> new NumericResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRangeByLex(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<ZRangeByLexCommand, ByteBuffer>> zRangeByLex(Publisher<ZRangeByLexCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Destination key must not be null!");
Observable<byte[]> result = Observable.empty();
String lowerBound = AgrumentConverters.lowerBoundArgOf(command.getRange()).toString();
String upperBound = AgrumentConverters.upperBoundArgOf(command.getRange()).toString();
if (command.getLimit() != null) {
if (ObjectUtils.nullSafeEquals(command.getDirection(), Direction.ASC)) {
result = cmd.zrangebylex(command.getKey().array(), lowerBound, upperBound, command.getLimit().getOffset(),
command.getLimit().getCount());
} else {
// TODO: fix when https://github.com/mp911de/lettuce/issues/369 resolved
throw new UnsupportedOperationException("Lettuce does not support ZREVRANGEBYLEX.");
}
} else {
if (ObjectUtils.nullSafeEquals(command.getDirection(), Direction.ASC)) {
result = cmd.zrangebylex(command.getKey().array(), lowerBound, upperBound);
} else {
// TODO: fix when https://github.com/mp911de/lettuce/issues/369 resolved
throw new UnsupportedOperationException("Lettuce does not support ZREVRANGEBYLEX.");
}
}
return LettuceReactiveRedisConnection.<List<ByteBuffer>> monoConverter()
.convert(result.map(ByteBuffer::wrap).toList()).map(value -> new MultiValueResponse<>(command, value));
}));
}
private ZStoreArgs zStoreArgs(Aggregate aggregate, List<Double> weights) {
ZStoreArgs args = new ZStoreArgs();
if (aggregate != null) {
switch (aggregate) {
case MIN:
args.min();
break;
case MAX:
args.max();
break;
default:
args.sum();
break;
}
}
// TODO: fix when https://github.com/mp911de/lettuce/issues/368 resolved
if (weights != null) {
long[] lg = new long[weights.size()];
for (int i = 0; i < lg.length; i++) {
lg[i] = weights.get(i).longValue();
}
args.weights(lg);
}
return args;
}
protected LettuceReactiveRedisConnection getConnection() {
return connection;
}
}