3232import org .springframework .data .redis .TooManyClusterRedirectionsException ;
3333import org .springframework .data .redis .connection .util .ByteArraySet ;
3434import org .springframework .data .redis .connection .util .ByteArrayWrapper ;
35+ import org .springframework .lang .NonNull ;
3536import org .springframework .lang .Nullable ;
3637import org .springframework .scheduling .concurrent .ThreadPoolTaskExecutor ;
3738import org .springframework .util .Assert ;
4849 */
4950public class ClusterCommandExecutor implements DisposableBean {
5051
51- private AsyncTaskExecutor executor ;
52- private final ClusterTopologyProvider topologyProvider ;
52+ protected static final AsyncTaskExecutor DEFAULT_TASK_EXECUTOR = new SimpleAsyncTaskExecutor ();
53+
54+ private int maxRedirects = 5 ;
55+
56+ private final AsyncTaskExecutor executor ;
57+
5358 private final ClusterNodeResourceProvider resourceProvider ;
59+
60+ private final ClusterTopologyProvider topologyProvider ;
61+
5462 private final ExceptionTranslationStrategy exceptionTranslationStrategy ;
55- private int maxRedirects = 5 ;
5663
5764 /**
5865 * Create a new instance of {@link ClusterCommandExecutor}.
@@ -64,13 +71,7 @@ public class ClusterCommandExecutor implements DisposableBean {
6471 public ClusterCommandExecutor (ClusterTopologyProvider topologyProvider , ClusterNodeResourceProvider resourceProvider ,
6572 ExceptionTranslationStrategy exceptionTranslation ) {
6673
67- Assert .notNull (topologyProvider , "ClusterTopologyProvider must not be null" );
68- Assert .notNull (resourceProvider , "ClusterNodeResourceProvider must not be null" );
69- Assert .notNull (exceptionTranslation , "ExceptionTranslationStrategy must not be null" );
70-
71- this .topologyProvider = topologyProvider ;
72- this .resourceProvider = resourceProvider ;
73- this .exceptionTranslationStrategy = exceptionTranslation ;
74+ this (topologyProvider , resourceProvider , exceptionTranslation , DEFAULT_TASK_EXECUTOR );
7475 }
7576
7677 /**
@@ -82,36 +83,42 @@ public ClusterCommandExecutor(ClusterTopologyProvider topologyProvider, ClusterN
8283 public ClusterCommandExecutor (ClusterTopologyProvider topologyProvider , ClusterNodeResourceProvider resourceProvider ,
8384 ExceptionTranslationStrategy exceptionTranslation , @ Nullable AsyncTaskExecutor executor ) {
8485
85- this (topologyProvider , resourceProvider , exceptionTranslation );
86- this .executor = executor ;
86+ Assert .notNull (topologyProvider , "ClusterTopologyProvider must not be null" );
87+ Assert .notNull (resourceProvider , "ClusterNodeResourceProvider must not be null" );
88+ Assert .notNull (exceptionTranslation , "ExceptionTranslationStrategy must not be null" );
89+
90+ this .topologyProvider = topologyProvider ;
91+ this .resourceProvider = resourceProvider ;
92+ this .exceptionTranslationStrategy = exceptionTranslation ;
93+ this .executor = resolveTaskExecutor (executor );
8794 }
8895
89- {
90- if (executor == null ) {
91- this .executor = new SimpleAsyncTaskExecutor ();
92- }
96+ private @ NonNull AsyncTaskExecutor resolveTaskExecutor (@ Nullable AsyncTaskExecutor taskExecutor ) {
97+ return taskExecutor != null ? taskExecutor : DEFAULT_TASK_EXECUTOR ;
9398 }
9499
95100 /**
96101 * Run {@link ClusterCommandCallback} on a random node.
97102 *
98- * @param cmd must not be {@literal null}.
103+ * @param commandCallback must not be {@literal null}.
99104 * @return never {@literal null}.
100105 */
101- public <T > NodeResult <T > executeCommandOnArbitraryNode (ClusterCommandCallback <?, T > cmd ) {
106+ public <T > NodeResult <T > executeCommandOnArbitraryNode (ClusterCommandCallback <?, T > commandCallback ) {
107+
108+ Assert .notNull (commandCallback , "ClusterCommandCallback must not be null" );
102109
103- Assert .notNull (cmd , "ClusterCommandCallback must not be null" );
104110 List <RedisClusterNode > nodes = new ArrayList <>(getClusterTopology ().getActiveNodes ());
105- return executeCommandOnSingleNode (cmd , nodes .get (new Random ().nextInt (nodes .size ())));
111+
112+ return executeCommandOnSingleNode (commandCallback , nodes .get (new Random ().nextInt (nodes .size ())));
106113 }
107114
108115 /**
109116 * Run {@link ClusterCommandCallback} on given {@link RedisClusterNode}.
110117 *
111118 * @param cmd must not be {@literal null}.
112119 * @param node must not be {@literal null}.
120+ * @return the {@link NodeResult} from the single, targeted {@link RedisClusterNode}.
113121 * @throws IllegalArgumentException in case no resource can be acquired for given node.
114- * @return
115122 */
116123 public <S , T > NodeResult <T > executeCommandOnSingleNode (ClusterCommandCallback <S , T > cmd , RedisClusterNode node ) {
117124 return executeCommandOnSingleNode (cmd , node , 0 );
@@ -132,19 +139,21 @@ private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S
132139 RedisClusterNode nodeToUse = lookupNode (node );
133140
134141 S client = this .resourceProvider .getResourceForSpecificNode (nodeToUse );
142+
135143 Assert .notNull (client , "Could not acquire resource for node; Is your cluster info up to date" );
136144
137145 try {
138146 return new NodeResult <>(node , cmd .doInCluster (client ));
139- } catch (RuntimeException ex ) {
147+ } catch (RuntimeException cause ) {
140148
141- RuntimeException translatedException = convertToDataAccessException (ex );
142- if (translatedException instanceof ClusterRedirectException ) {
143- ClusterRedirectException cre = (ClusterRedirectException ) translatedException ;
144- return executeCommandOnSingleNode (cmd ,
145- topologyProvider .getTopology ().lookup (cre .getTargetHost (), cre .getTargetPort ()), redirectCount + 1 );
149+ RuntimeException translatedException = convertToDataAccessException (cause );
150+
151+ if (translatedException instanceof ClusterRedirectException clusterRedirectException ) {
152+ return executeCommandOnSingleNode (cmd , topologyProvider .getTopology ()
153+ .lookup (clusterRedirectException .getTargetHost (), clusterRedirectException .getTargetPort ()),
154+ redirectCount + 1 );
146155 } else {
147- throw translatedException != null ? translatedException : ex ;
156+ throw translatedException != null ? translatedException : cause ;
148157 }
149158 } finally {
150159 this .resourceProvider .returnResourceForSpecificNode (nodeToUse , client );
@@ -159,10 +168,11 @@ private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S
159168 * @throws IllegalArgumentException in case the node could not be resolved to a topology-known node
160169 */
161170 private RedisClusterNode lookupNode (RedisClusterNode node ) {
171+
162172 try {
163173 return topologyProvider .getTopology ().lookup (node );
164- } catch (ClusterStateFailureException e ) {
165- throw new IllegalArgumentException (String .format ("Node %s is unknown to cluster" , node ), e );
174+ } catch (ClusterStateFailureException cause ) {
175+ throw new IllegalArgumentException (String .format ("Node %s is unknown to cluster" , node ), cause );
166176 }
167177 }
168178
@@ -171,7 +181,8 @@ private RedisClusterNode lookupNode(RedisClusterNode node) {
171181 *
172182 * @param cmd must not be {@literal null}.
173183 * @return never {@literal null}.
174- * @throws ClusterCommandExecutionFailureException
184+ * @throws ClusterCommandExecutionFailureException if a failure occurs while executing the given
185+ * {@link ClusterCommandCallback command} on any given {@link RedisClusterNode node}.
175186 */
176187 public <S , T > MultiNodeResult <T > executeCommandOnAllNodes (final ClusterCommandCallback <S , T > cmd ) {
177188 return executeCommandAsyncOnNodes (cmd , getClusterTopology ().getActiveMasterNodes ());
@@ -181,7 +192,8 @@ public <S, T> MultiNodeResult<T> executeCommandOnAllNodes(final ClusterCommandCa
181192 * @param callback must not be {@literal null}.
182193 * @param nodes must not be {@literal null}.
183194 * @return never {@literal null}.
184- * @throws ClusterCommandExecutionFailureException
195+ * @throws ClusterCommandExecutionFailureException if a failure occurs while executing the given
196+ * {@link ClusterCommandCallback command} on any given {@link RedisClusterNode node}.
185197 * @throws IllegalArgumentException in case the node could not be resolved to a topology-known node
186198 */
187199 public <S , T > MultiNodeResult <T > executeCommandAsyncOnNodes (ClusterCommandCallback <S , T > callback ,
@@ -202,6 +214,7 @@ public <S, T> MultiNodeResult<T> executeCommandAsyncOnNodes(ClusterCommandCallba
202214 }
203215
204216 Map <NodeExecution , Future <NodeResult <T >>> futures = new LinkedHashMap <>();
217+
205218 for (RedisClusterNode node : resolvedRedisClusterNodes ) {
206219 futures .put (new NodeExecution (node ), executor .submit (() -> executeCommandOnSingleNode (callback , node )));
207220 }
@@ -213,10 +226,10 @@ private <T> MultiNodeResult<T> collectResults(Map<NodeExecution, Future<NodeResu
213226
214227 boolean done = false ;
215228
216- MultiNodeResult <T > result = new MultiNodeResult <>();
217229 Map <RedisClusterNode , Throwable > exceptions = new HashMap <>();
218-
230+ MultiNodeResult < T > result = new MultiNodeResult <>();
219231 Set <String > saveGuard = new HashSet <>();
232+
220233 while (!done ) {
221234
222235 done = true ;
@@ -242,6 +255,7 @@ private <T> MultiNodeResult<T> collectResults(Map<NodeExecution, Future<NodeResu
242255 } catch (ExecutionException e ) {
243256
244257 RuntimeException ex = convertToDataAccessException ((Exception ) e .getCause ());
258+
245259 exceptions .put (execution .getNode (), ex != null ? ex : e .getCause ());
246260 } catch (InterruptedException e ) {
247261
@@ -253,6 +267,7 @@ private <T> MultiNodeResult<T> collectResults(Map<NodeExecution, Future<NodeResu
253267 }
254268 }
255269 }
270+
256271 try {
257272 Thread .sleep (10 );
258273 } catch (InterruptedException e ) {
@@ -273,21 +288,23 @@ private <T> MultiNodeResult<T> collectResults(Map<NodeExecution, Future<NodeResu
273288 *
274289 * @param cmd must not be {@literal null}.
275290 * @return never {@literal null}.
276- * @throws ClusterCommandExecutionFailureException
291+ * @throws ClusterCommandExecutionFailureException if a failure occurs while executing the given
292+ * {@link MultiKeyClusterCommandCallback command}.
277293 */
278294 public <S , T > MultiNodeResult <T > executeMultiKeyCommand (MultiKeyClusterCommandCallback <S , T > cmd ,
279295 Iterable <byte []> keys ) {
280296
281297 Map <RedisClusterNode , PositionalKeys > nodeKeyMap = new HashMap <>();
282-
283298 int index = 0 ;
299+
284300 for (byte [] key : keys ) {
285301 for (RedisClusterNode node : getClusterTopology ().getKeyServingNodes (key )) {
286302 nodeKeyMap .computeIfAbsent (node , val -> PositionalKeys .empty ()).append (PositionalKey .of (key , index ++));
287303 }
288304 }
289305
290306 Map <NodeExecution , Future <NodeResult <T >>> futures = new LinkedHashMap <>();
307+
291308 for (Entry <RedisClusterNode , PositionalKeys > entry : nodeKeyMap .entrySet ()) {
292309
293310 if (entry .getKey ().isMaster ()) {
@@ -309,6 +326,7 @@ private <S, T> NodeResult<T> executeMultiKeyCommandOnSingleNode(MultiKeyClusterC
309326 Assert .notNull (key , "Keys for execution must not be null" );
310327
311328 S client = this .resourceProvider .getResourceForSpecificNode (node );
329+
312330 Assert .notNull (client , "Could not acquire resource for node; Is your cluster info up to date" );
313331
314332 try {
@@ -479,7 +497,9 @@ public RedisClusterNode getNode() {
479497 }
480498
481499 /**
482- * @return
500+ * Returns the key as an array of bytes.
501+ *
502+ * @return the key as an array of bytes.
483503 */
484504 public byte [] getKey () {
485505 return key .getArray ();
0 commit comments