diff --git a/src/main/java/io/lettuce/core/api/parsers/tracking/TrackingInfo.java b/src/main/java/io/lettuce/core/TrackingInfo.java similarity index 99% rename from src/main/java/io/lettuce/core/api/parsers/tracking/TrackingInfo.java rename to src/main/java/io/lettuce/core/TrackingInfo.java index 892be765ec..7c11da7442 100644 --- a/src/main/java/io/lettuce/core/api/parsers/tracking/TrackingInfo.java +++ b/src/main/java/io/lettuce/core/TrackingInfo.java @@ -32,7 +32,7 @@ * command. * * @author Tihomir Mateev - * @since 7.0 + * @since 6.5 */ public class TrackingInfo { diff --git a/src/main/java/io/lettuce/core/output/data/ArrayAggregateData.java b/src/main/java/io/lettuce/core/output/ArrayComplexData.java similarity index 96% rename from src/main/java/io/lettuce/core/output/data/ArrayAggregateData.java rename to src/main/java/io/lettuce/core/output/ArrayComplexData.java index 259e711239..673f1d1576 100644 --- a/src/main/java/io/lettuce/core/output/data/ArrayAggregateData.java +++ b/src/main/java/io/lettuce/core/output/ArrayComplexData.java @@ -18,7 +18,7 @@ * limitations under the License. */ -package io.lettuce.core.output.data; +package io.lettuce.core.output; import java.util.ArrayList; import java.util.Collections; @@ -41,9 +41,9 @@ * * @see DynamicAggregateData * @author Tihomir Mateev - * @since 7.0 + * @since 6.5 */ -public class ArrayAggregateData extends DynamicAggregateData { +class ArrayAggregateData extends DynamicAggregateData { private final List data; diff --git a/src/main/java/io/lettuce/core/output/data/DynamicAggregateData.java b/src/main/java/io/lettuce/core/output/ComplexData.java similarity index 99% rename from src/main/java/io/lettuce/core/output/data/DynamicAggregateData.java rename to src/main/java/io/lettuce/core/output/ComplexData.java index dc494b3bdf..46c111e0ed 100644 --- a/src/main/java/io/lettuce/core/output/data/DynamicAggregateData.java +++ b/src/main/java/io/lettuce/core/output/ComplexData.java @@ -18,7 +18,7 @@ * limitations under the License. */ -package io.lettuce.core.output.data; +package io.lettuce.core.output; import java.util.List; import java.util.Map; @@ -57,7 +57,7 @@ * @see SetAggregateData * @see MapAggregateData * @author Tihomir Mateev - * @since 7.0 + * @since 6.5 */ public abstract class DynamicAggregateData { diff --git a/src/main/java/io/lettuce/core/api/parsers/tracking/package-info.java b/src/main/java/io/lettuce/core/output/ComplexDataParser.java similarity index 52% rename from src/main/java/io/lettuce/core/api/parsers/tracking/package-info.java rename to src/main/java/io/lettuce/core/output/ComplexDataParser.java index 11ff697bb7..1f0a85c759 100644 --- a/src/main/java/io/lettuce/core/api/parsers/tracking/package-info.java +++ b/src/main/java/io/lettuce/core/output/ComplexDataParser.java @@ -18,7 +18,27 @@ * limitations under the License. */ +package io.lettuce.core.output; + /** - * Model and parser for the {@code CLIENT TRACKINGINFO} output. + * Any usage of the {@link DynamicAggregateOutput} comes hand in hand with a respective {@link DynamicAggregateDataParser} that + * is able to parse the data extracted from the server to a meaningful Java object. + * + * @param the type of the parsed object + * @author Tihomir Mateev + * @see DynamicAggregateData + * @see DynamicAggregateOutput + * @since 6.5 */ -package io.lettuce.core.api.parsers.tracking; +public interface DynamicAggregateDataParser { + + /** + * Parse the data extracted from the server to a specific domain object. + * + * @param data the data to parse + * @return the parsed object + * @since 6.5 + */ + T parse(DynamicAggregateData data); + +} diff --git a/src/main/java/io/lettuce/core/output/DynamicAggregateOutput.java b/src/main/java/io/lettuce/core/output/ComplexOutput.java similarity index 57% rename from src/main/java/io/lettuce/core/output/DynamicAggregateOutput.java rename to src/main/java/io/lettuce/core/output/ComplexOutput.java index 2e4fd5c8d0..10c853192f 100644 --- a/src/main/java/io/lettuce/core/output/DynamicAggregateOutput.java +++ b/src/main/java/io/lettuce/core/output/ComplexOutput.java @@ -23,118 +23,115 @@ import io.lettuce.core.codec.RedisCodec; import io.lettuce.core.codec.StringCodec; import io.lettuce.core.internal.LettuceFactories; -import io.lettuce.core.output.data.DynamicAggregateData; -import io.lettuce.core.output.data.ArrayAggregateData; -import io.lettuce.core.output.data.MapAggregateData; -import io.lettuce.core.output.data.SetAggregateData; import java.nio.ByteBuffer; import java.util.Deque; /** - * An implementation of the {@link CommandOutput} that heuristically attempts to parse any response the Redis server could - * provide, leaving out the user to provide the knowledge of how this data is to be processed + * An implementation of the {@link CommandOutput} that is used in combination with a given {@link DynamicAggregateDataParser} to + * produce a domain object from the data extracted from the server. Since there already are implementations of the + * {@link CommandOutput} interface for most simple types, this implementation is better suited to parse complex, often nested, + * data structures, for example a map containing other maps, arrays or sets as values for one or more of its keys. *

- * The Redis server could return, besides the simple types such as boolean, long, String, etc. also aggregations of the former, - * inside aggregate data structures such as arrays, maps and sets, defined in the specification for both the - * RESP2 and - * RESP3 protocol. - *

- * Commands typically result in simple types, however some of the commands could return complex nested structures. A simple - * solution to parse such a structure is to have a dynamic data type and leave the user to parse the result to a domain object. - * If there is some breach of contract then the code consuming the driver could simply stop using the provided parser and start - * parsing the new dynamic data itself, without having to change the version of the library. This allows a certain degree of - * stability against change. Consult the members of the {@link io.lettuce.core.api.parsers} package for details on how aggregate - * Objects could be parsed. - *

- * The {@link DynamicAggregateOutput} is supposed to be compatible with all Redis commands. + * The implementation of the {@link DynamicAggregateDataParser} is responsible for mapping the data from the result to + * meaningful properties that the user of the LEttuce driver could then use in a statically typed manner. * - * @see DynamicAggregateData + * @see DynamicAggregateDataParser * @author Tihomir Mateev - * @since 7.0 + * @since 6.5 */ -public class DynamicAggregateOutput extends CommandOutput { +public class DynamicAggregateOutput extends CommandOutput { private final Deque dataStack; + private final DynamicAggregateDataParser parser; + + private DynamicAggregateData data; + /** * Constructs a new instance of the {@link DynamicAggregateOutput} * * @param codec the {@link RedisCodec} to be applied */ - public DynamicAggregateOutput(RedisCodec codec) { + public DynamicAggregateOutput(RedisCodec codec, DynamicAggregateDataParser parser) { super(codec, null); dataStack = LettuceFactories.newSpScQueue(); + this.parser = parser; + } + + @Override + public T get() { + return parser.parse(data); } @Override public void set(long integer) { - if (output == null) { + if (data == null) { throw new RuntimeException("Invalid output received for dynamic aggregate output." + "Integer value should have been preceded by some sort of aggregation."); } - output.store(integer); + data.store(integer); } @Override public void set(double number) { - if (output == null) { + if (data == null) { throw new RuntimeException("Invalid output received for dynamic aggregate output." + "Double value should have been preceded by some sort of aggregation."); } - output.store(number); + data.store(number); } @Override public void set(boolean value) { - if (output == null) { + if (data == null) { throw new RuntimeException("Invalid output received for dynamic aggregate output." + "Double value should have been preceded by some sort of aggregation."); } - output.store(value); + data.store(value); } @Override public void set(ByteBuffer bytes) { - if (output == null) { + if (data == null) { throw new RuntimeException("Invalid output received for dynamic aggregate output." + "ByteBuffer value should have been preceded by some sort of aggregation."); } - output.storeObject(bytes == null ? null : codec.decodeValue(bytes)); + data.storeObject(bytes == null ? null : codec.decodeValue(bytes)); } @Override public void setSingle(ByteBuffer bytes) { - if (output == null) { + if (data == null) { throw new RuntimeException("Invalid output received for dynamic aggregate output." + "String value should have been preceded by some sort of aggregation."); } - output.store(bytes == null ? null : StringCodec.UTF8.decodeValue(bytes)); + data.store(bytes == null ? null : StringCodec.UTF8.decodeValue(bytes)); } @Override public void complete(int depth) { if (!dataStack.isEmpty() && depth == dataStack.size()) { - output = dataStack.pop(); + data = dataStack.pop(); } } - private void multi(DynamicAggregateData data) { + private void multi(DynamicAggregateData newData) { // if there is no data set, then we are at the root object - if (output == null) { - output = data; + if (data == null) { + data = newData; return; } // otherwise we need to nest the provided structure - output.storeObject(data); - dataStack.push(output); - output = data; + data.storeObject(newData); + dataStack.push(data); + data = newData; } @Override diff --git a/src/main/java/io/lettuce/core/output/data/MapAggregateData.java b/src/main/java/io/lettuce/core/output/MapComplexData.java similarity index 93% rename from src/main/java/io/lettuce/core/output/data/MapAggregateData.java rename to src/main/java/io/lettuce/core/output/MapComplexData.java index 0f42baabd4..bb908aa9b5 100644 --- a/src/main/java/io/lettuce/core/output/data/MapAggregateData.java +++ b/src/main/java/io/lettuce/core/output/MapComplexData.java @@ -18,7 +18,7 @@ * limitations under the License. */ -package io.lettuce.core.output.data; +package io.lettuce.core.output; import java.util.Collections; import java.util.HashMap; @@ -31,9 +31,9 @@ * * @see DynamicAggregateData * @author Tihomir Mateev - * @since 7.0 + * @since 6.5 */ -public class MapAggregateData extends DynamicAggregateData { +class MapAggregateData extends DynamicAggregateData { private final Map data; diff --git a/src/main/java/io/lettuce/core/output/data/SetAggregateData.java b/src/main/java/io/lettuce/core/output/SetComplexData.java similarity index 97% rename from src/main/java/io/lettuce/core/output/data/SetAggregateData.java rename to src/main/java/io/lettuce/core/output/SetComplexData.java index 3a0718ab9b..ff15caf640 100644 --- a/src/main/java/io/lettuce/core/output/data/SetAggregateData.java +++ b/src/main/java/io/lettuce/core/output/SetComplexData.java @@ -18,7 +18,7 @@ * limitations under the License. */ -package io.lettuce.core.output.data; +package io.lettuce.core.output; import java.util.ArrayList; import java.util.Collections; @@ -33,7 +33,7 @@ * * @see DynamicAggregateData * @author Tihomir Mateev - * @since 7.0 + * @since 6.5 */ public class SetAggregateData extends DynamicAggregateData { diff --git a/src/main/java/io/lettuce/core/api/parsers/tracking/TrackingInfoParser.java b/src/main/java/io/lettuce/core/output/TrackingInfoParser.java similarity index 83% rename from src/main/java/io/lettuce/core/api/parsers/tracking/TrackingInfoParser.java rename to src/main/java/io/lettuce/core/output/TrackingInfoParser.java index 38a92c0ee3..c9c1dbc2a8 100644 --- a/src/main/java/io/lettuce/core/api/parsers/tracking/TrackingInfoParser.java +++ b/src/main/java/io/lettuce/core/output/TrackingInfoParser.java @@ -18,9 +18,10 @@ * limitations under the License. */ -package io.lettuce.core.api.parsers.tracking; +package io.lettuce.core.output; -import io.lettuce.core.output.data.DynamicAggregateData; +import io.lettuce.core.output.DynamicAggregateDataParser; +import io.lettuce.core.output.DynamicAggregateData; import io.lettuce.core.protocol.CommandKeyword; import java.util.ArrayList; @@ -35,7 +36,9 @@ * @author Tihomir Mateev * @since 7.0 */ -public class TrackingInfoParser { +public class TrackingInfoParser implements DynamicAggregateDataParser { + + public static final TrackingInfoParser INSTANCE = new TrackingInfoParser(); /** * Utility constructor. @@ -46,11 +49,11 @@ private TrackingInfoParser() { /** * Parse the output of the Redis CLIENT TRACKINGINFO command and convert it to a {@link TrackingInfo} * - * @param trackinginfoOutput output of CLIENT TRACKINGINFO command + * @param dynamicData output of CLIENT TRACKINGINFO command * @return an {@link TrackingInfo} instance */ - public static TrackingInfo parse(DynamicAggregateData trackinginfoOutput) { - Map data = verifyStructure(trackinginfoOutput); + public TrackingInfo parse(DynamicAggregateData dynamicData) { + Map data = verifyStructure(dynamicData); Set flags = ((DynamicAggregateData) data.get(CommandKeyword.FLAGS.toString().toLowerCase())).getDynamicSet(); Long clientId = (Long) data.get(CommandKeyword.REDIRECT.toString().toLowerCase()); List prefixes = ((DynamicAggregateData) data.get(CommandKeyword.PREFIXES.toString().toLowerCase())).getDynamicList(); @@ -70,7 +73,7 @@ public static TrackingInfo parse(DynamicAggregateData trackinginfoOutput) { return new TrackingInfo(parsedFlags, clientId, parsedPrefixes); } - private static Map verifyStructure(DynamicAggregateData trackinginfoOutput) { + private Map verifyStructure(DynamicAggregateData trackinginfoOutput) { if (trackinginfoOutput == null) { throw new IllegalArgumentException("Failed while parsing CLIENT TRACKINGINFO: trackinginfoOutput must not be null"); diff --git a/src/test/java/io/lettuce/core/cluster/models/tracking/TrackingInfoParserTest.java b/src/test/java/io/lettuce/core/output/TrackingInfoParserTest.java similarity index 92% rename from src/test/java/io/lettuce/core/cluster/models/tracking/TrackingInfoParserTest.java rename to src/test/java/io/lettuce/core/output/TrackingInfoParserTest.java index d9499fdccb..ede4f6394d 100644 --- a/src/test/java/io/lettuce/core/cluster/models/tracking/TrackingInfoParserTest.java +++ b/src/test/java/io/lettuce/core/output/TrackingInfoParserTest.java @@ -1,11 +1,11 @@ -package io.lettuce.core.cluster.models.tracking; +package io.lettuce.core.output; import io.lettuce.core.api.parsers.tracking.TrackingInfo; import io.lettuce.core.api.parsers.tracking.TrackingInfoParser; -import io.lettuce.core.output.data.ArrayAggregateData; -import io.lettuce.core.output.data.DynamicAggregateData; -import io.lettuce.core.output.data.MapAggregateData; -import io.lettuce.core.output.data.SetAggregateData; +import io.lettuce.core.output.ArrayAggregateData; +import io.lettuce.core.output.DynamicAggregateData; +import io.lettuce.core.output.MapAggregateData; +import io.lettuce.core.output.SetAggregateData; import io.lettuce.core.protocol.CommandKeyword; import org.junit.jupiter.api.Test;