29
29
30
30
import java .lang .reflect .Field ;
31
31
import 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 ;
35
34
import java .util .function .Supplier ;
35
+ import java .util .stream .Collectors ;
36
36
37
37
import org .springframework .beans .DirectFieldAccessor ;
38
- import org .springframework .lang .Nullable ;
39
38
import org .springframework .util .ClassUtils ;
40
39
import org .springframework .util .ReflectionUtils ;
41
40
42
41
/**
42
+ * Utility class to dispatch arbitrary Redis commands using Jedis commands.
43
+ *
43
44
* @author Christoph Strobl
45
+ * @author Mark Paluch
44
46
* @since 2.1
45
47
*/
48
+ @ SuppressWarnings ({ "unchecked" , "ConstantConditions" })
46
49
class JedisClientUtils {
47
50
48
51
private static final Field CLIENT_FIELD ;
49
52
private static final Method SEND_COMMAND ;
50
53
private static final Method GET_RESPONSE ;
51
54
private static final Method PROTOCOL_SEND_COMMAND ;
55
+ private static final Set <String > KNOWN_COMMANDS ;
56
+ private static final Builder <Object > OBJECT_BUILDER ;
52
57
53
58
static {
54
59
@@ -60,12 +65,12 @@ class JedisClientUtils {
60
65
ReflectionUtils .makeAccessible (PROTOCOL_SEND_COMMAND );
61
66
62
67
try {
68
+
63
69
Class <?> commandType = ClassUtils .isPresent ("redis.clients.jedis.ProtocolCommand" , null )
64
70
? ClassUtils .forName ("redis.clients.jedis.ProtocolCommand" , null )
65
71
: ClassUtils .forName ("redis.clients.jedis.Protocol$Command" , null );
66
72
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 );
69
74
} catch (Exception e ) {
70
75
throw new NoClassDefFoundError (
71
76
"Could not find required flavor of command required by 'redis.clients.jedis.Connection#sendCommand'." );
@@ -75,38 +80,56 @@ class JedisClientUtils {
75
80
76
81
GET_RESPONSE = ReflectionUtils .findMethod (Queable .class , "getResponse" , Builder .class );
77
82
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
+ };
78
95
}
79
96
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 ) {
82
107
83
- List <byte []> mArgs = new ArrayList <>(keys );
84
- mArgs .addAll (args );
108
+ byte [][] commandArgs = getCommandArguments (keys , args );
85
109
86
- Client client = retrieveClient (jedis .get ());
87
- sendCommand (client , command , mArgs .toArray (new byte [mArgs .size ()][]));
110
+ Client client = sendCommand (command , commandArgs , jedis .get ());
88
111
89
112
return (T ) client .getOne ();
90
113
}
91
114
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 ) {
97
124
98
125
Client client = retrieveClient (jedis );
99
126
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 );
105
128
106
129
return client ;
107
130
}
108
131
109
- static void sendCommand (Client client , String command , byte [][] args ) {
132
+ private static void sendCommand (Client client , String command , byte [][] args ) {
110
133
111
134
if (isKnownCommand (command )) {
112
135
ReflectionUtils .invokeMethod (SEND_COMMAND , client , Command .valueOf (command .trim ().toUpperCase ()), args );
@@ -115,8 +138,9 @@ static void sendCommand(Client client, String command, byte[][] args) {
115
138
}
116
139
}
117
140
118
- static void sendProtocolCommand (Client client , String command , byte [][] args ) {
141
+ private static void sendProtocolCommand (Client client , String command , byte [][] args ) {
119
142
143
+ // quite expensive to construct for each command invocation
120
144
DirectFieldAccessor dfa = new DirectFieldAccessor (client );
121
145
122
146
client .connect ();
@@ -125,34 +149,52 @@ static void sendProtocolCommand(Client client, String command, byte[][] args) {
125
149
ReflectionUtils .invokeMethod (PROTOCOL_SEND_COMMAND , null , os , SafeEncoder .encode (command ), args );
126
150
127
151
Integer pipelinedCommands = (Integer ) dfa .getPropertyValue ("pipelinedCommands" );
128
- dfa .setPropertyValue ("pipelinedCommands" , pipelinedCommands . intValue () + 1 );
152
+ dfa .setPropertyValue ("pipelinedCommands" , pipelinedCommands + 1 );
129
153
}
130
154
131
- static boolean isKnownCommand (String command ) {
155
+ private static boolean isKnownCommand (String command ) {
156
+ return KNOWN_COMMANDS .contains (command );
157
+ }
132
158
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 ;
138
167
}
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 ;
139
175
}
140
176
177
+ /**
178
+ * @param jedis the client instance.
179
+ * @return {@literal true} if the connection has entered {@literal MULTI} state.
180
+ */
141
181
static boolean isInMulti (Jedis jedis ) {
142
182
return retrieveClient (jedis ).isInMulti ();
143
183
}
144
184
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 );
156
195
}
157
196
197
+ private static Client retrieveClient (Jedis jedis ) {
198
+ return (Client ) ReflectionUtils .getField (CLIENT_FIELD , jedis );
199
+ }
158
200
}
0 commit comments