2929
3030import java .lang .reflect .Field ;
3131import java .lang .reflect .Method ;
32- import java .util .ArrayList ;
33- import java .util .Collection ;
34- import java .util .List ;
32+ import java .util .Arrays ;
33+ import java .util .Set ;
3534import java .util .function .Supplier ;
35+ import java .util .stream .Collectors ;
3636
3737import org .springframework .beans .DirectFieldAccessor ;
38- import org .springframework .lang .Nullable ;
3938import org .springframework .util .ClassUtils ;
4039import org .springframework .util .ReflectionUtils ;
4140
4241/**
42+ * Utility class to dispatch arbitrary Redis commands using Jedis commands.
43+ *
4344 * @author Christoph Strobl
45+ * @author Mark Paluch
4446 * @since 2.1
4547 */
48+ @ SuppressWarnings ({ "unchecked" , "ConstantConditions" })
4649class JedisClientUtils {
4750
4851 private static final Field CLIENT_FIELD ;
4952 private static final Method SEND_COMMAND ;
5053 private static final Method GET_RESPONSE ;
5154 private static final Method PROTOCOL_SEND_COMMAND ;
55+ private static final Set <String > KNOWN_COMMANDS ;
56+ private static final Builder <Object > OBJECT_BUILDER ;
5257
5358 static {
5459
@@ -60,12 +65,12 @@ class JedisClientUtils {
6065 ReflectionUtils .makeAccessible (PROTOCOL_SEND_COMMAND );
6166
6267 try {
68+
6369 Class <?> commandType = ClassUtils .isPresent ("redis.clients.jedis.ProtocolCommand" , null )
6470 ? ClassUtils .forName ("redis.clients.jedis.ProtocolCommand" , null )
6571 : ClassUtils .forName ("redis.clients.jedis.Protocol$Command" , null );
6672
67- SEND_COMMAND = ReflectionUtils .findMethod (Connection .class , "sendCommand" ,
68- new Class [] { commandType , byte [][].class });
73+ SEND_COMMAND = ReflectionUtils .findMethod (Connection .class , "sendCommand" , commandType , byte [][].class );
6974 } catch (Exception e ) {
7075 throw new NoClassDefFoundError (
7176 "Could not find required flavor of command required by 'redis.clients.jedis.Connection#sendCommand'." );
@@ -75,38 +80,56 @@ class JedisClientUtils {
7580
7681 GET_RESPONSE = ReflectionUtils .findMethod (Queable .class , "getResponse" , Builder .class );
7782 ReflectionUtils .makeAccessible (GET_RESPONSE );
83+
84+ KNOWN_COMMANDS = Arrays .stream (Command .values ()).map (Enum ::name ).collect (Collectors .toSet ());
85+
86+ OBJECT_BUILDER = new Builder <Object >() {
87+ public Object build (Object data ) {
88+ return data ;
89+ }
90+
91+ public String toString () {
92+ return "Object" ;
93+ }
94+ };
7895 }
7996
80- @ Nullable
81- static <T > T execute (String command , Collection <byte []> keys , Collection <byte []> args , Supplier <Jedis > jedis ) {
97+ /**
98+ * Execute an arbitrary on the supplied {@link Jedis} instance.
99+ *
100+ * @param command the command.
101+ * @param keys must not be {@literal null}, may be empty.
102+ * @param args must not be {@literal null}, may be empty.
103+ * @param jedis must not be {@literal null}.
104+ * @return the response, can be be {@literal null}.
105+ */
106+ static <T > T execute (String command , byte [][] keys , byte [][] args , Supplier <Jedis > jedis ) {
82107
83- List <byte []> mArgs = new ArrayList <>(keys );
84- mArgs .addAll (args );
108+ byte [][] commandArgs = getCommandArguments (keys , args );
85109
86- Client client = retrieveClient (jedis .get ());
87- sendCommand (client , command , mArgs .toArray (new byte [mArgs .size ()][]));
110+ Client client = sendCommand (command , commandArgs , jedis .get ());
88111
89112 return (T ) client .getOne ();
90113 }
91114
92- static Client retrieveClient (Jedis jedis ) {
93- return (Client ) ReflectionUtils .getField (CLIENT_FIELD , jedis );
94- }
95-
96- static Client sendCommand (Jedis jedis , String command , byte [][] args ) {
115+ /**
116+ * Send a Redis command and retrieve the {@link Client} for response retrieval.
117+ *
118+ * @param command the command.
119+ * @param args must not be {@literal null}, may be empty.
120+ * @param jedis must not be {@literal null}.
121+ * @return the {@link Client} instance used to send the command.
122+ */
123+ static Client sendCommand (String command , byte [][] args , Jedis jedis ) {
97124
98125 Client client = retrieveClient (jedis );
99126
100- if (isKnownCommand (command )) {
101- ReflectionUtils .invokeMethod (SEND_COMMAND , client , Command .valueOf (command .trim ().toUpperCase ()), args );
102- } else {
103- sendProtocolCommand (client , command , args );
104- }
127+ sendCommand (client , command , args );
105128
106129 return client ;
107130 }
108131
109- static void sendCommand (Client client , String command , byte [][] args ) {
132+ private static void sendCommand (Client client , String command , byte [][] args ) {
110133
111134 if (isKnownCommand (command )) {
112135 ReflectionUtils .invokeMethod (SEND_COMMAND , client , Command .valueOf (command .trim ().toUpperCase ()), args );
@@ -115,8 +138,9 @@ static void sendCommand(Client client, String command, byte[][] args) {
115138 }
116139 }
117140
118- static void sendProtocolCommand (Client client , String command , byte [][] args ) {
141+ private static void sendProtocolCommand (Client client , String command , byte [][] args ) {
119142
143+ // quite expensive to construct for each command invocation
120144 DirectFieldAccessor dfa = new DirectFieldAccessor (client );
121145
122146 client .connect ();
@@ -125,34 +149,52 @@ static void sendProtocolCommand(Client client, String command, byte[][] args) {
125149 ReflectionUtils .invokeMethod (PROTOCOL_SEND_COMMAND , null , os , SafeEncoder .encode (command ), args );
126150
127151 Integer pipelinedCommands = (Integer ) dfa .getPropertyValue ("pipelinedCommands" );
128- dfa .setPropertyValue ("pipelinedCommands" , pipelinedCommands . intValue () + 1 );
152+ dfa .setPropertyValue ("pipelinedCommands" , pipelinedCommands + 1 );
129153 }
130154
131- static boolean isKnownCommand (String command ) {
155+ private static boolean isKnownCommand (String command ) {
156+ return KNOWN_COMMANDS .contains (command );
157+ }
132158
133- try {
134- Command .valueOf (command );
135- return true ;
136- } catch (IllegalArgumentException e ) {
137- return false ;
159+ private static byte [][] getCommandArguments (byte [][] keys , byte [][] args ) {
160+
161+ if (keys .length == 0 ) {
162+ return args ;
163+ }
164+
165+ if (args .length == 0 ) {
166+ return keys ;
138167 }
168+
169+ byte [][] commandArgs = new byte [keys .length + args .length ][];
170+
171+ System .arraycopy (keys , 0 , commandArgs , 0 , keys .length );
172+ System .arraycopy (args , 0 , commandArgs , keys .length , args .length );
173+
174+ return commandArgs ;
139175 }
140176
177+ /**
178+ * @param jedis the client instance.
179+ * @return {@literal true} if the connection has entered {@literal MULTI} state.
180+ */
141181 static boolean isInMulti (Jedis jedis ) {
142182 return retrieveClient (jedis ).isInMulti ();
143183 }
144184
145- static Response <Object > getGetResponse (Object target ) {
146-
147- return (Response <Object >) ReflectionUtils .invokeMethod (GET_RESPONSE , target , new Builder <Object >() {
148- public Object build (Object data ) {
149- return data ;
150- }
151-
152- public String toString () {
153- return "Object" ;
154- }
155- });
185+ /**
186+ * Retrieve the {@link Response} object from a {@link redis.clients.jedis.Transaction} or a
187+ * {@link redis.clients.jedis.Pipeline} for response synchronization.
188+ *
189+ * @param target a {@link redis.clients.jedis.Transaction} or {@link redis.clients.jedis.Pipeline}, must not be
190+ * {@literal null}.
191+ * @return the {@link Response} wrapper object.
192+ */
193+ static Response <Object > getResponse (Object target ) {
194+ return (Response <Object >) ReflectionUtils .invokeMethod (GET_RESPONSE , target , OBJECT_BUILDER );
156195 }
157196
197+ private static Client retrieveClient (Jedis jedis ) {
198+ return (Client ) ReflectionUtils .getField (CLIENT_FIELD , jedis );
199+ }
158200}
0 commit comments