4545import java .net .URL ;
4646import java .util .ArrayList ;
4747import java .util .Arrays ;
48+ import java .util .Date ;
4849import java .util .HashMap ;
4950import java .util .List ;
5051import java .util .Map ;
@@ -87,6 +88,16 @@ public int hashCode() {
8788 }
8889 }
8990
91+ private static class HostStatus {
92+ boolean isUp = true ;
93+ long lastTryTimestamp ;
94+
95+ HostStatus (boolean isUp ) {
96+ this .isUp = isUp ;
97+ lastTryTimestamp = new Date ().getTime ();
98+ }
99+ }
100+
90101 // ----------------------------------------------------------------------
91102 // Constants
92103 // ----------------------------------------------------------------------
@@ -116,10 +127,14 @@ public int hashCode() {
116127 /** Read timeout for search requests (ms). */
117128 private int searchTimeout = 5000 ;
118129
130+ /** Delay to wait when a host is down before retrying it (ms). */
131+ private int hostDownDelay = 5000 ;
132+
119133 private final String applicationID ;
120134 private final String apiKey ;
121135 private List <String > readHosts ;
122136 private List <String > writeHosts ;
137+ private HashMap <String , HostStatus > hostStatuses = new HashMap <>();
123138
124139 /**
125140 * HTTP headers that will be sent with every request.
@@ -235,8 +250,7 @@ public int getConnectTimeout() {
235250 * @param connectTimeout The new connection timeout (ms).
236251 */
237252 public void setConnectTimeout (int connectTimeout ) {
238- if (connectTimeout <= 0 )
239- throw new IllegalArgumentException ();
253+ checkTimeout (connectTimeout );
240254 this .connectTimeout = connectTimeout ;
241255 }
242256
@@ -255,8 +269,7 @@ public int getReadTimeout() {
255269 * @param readTimeout The default read timeout (ms).
256270 */
257271 public void setReadTimeout (int readTimeout ) {
258- if (readTimeout <= 0 )
259- throw new IllegalArgumentException ();
272+ checkTimeout (readTimeout );
260273 this .readTimeout = readTimeout ;
261274 }
262275
@@ -275,11 +288,29 @@ public int getSearchTimeout() {
275288 * @param searchTimeout The read timeout for search requests (ms).
276289 */
277290 public void setSearchTimeout (int searchTimeout ) {
278- if (searchTimeout <= 0 )
279- throw new IllegalArgumentException ();
291+ checkTimeout (searchTimeout );
280292 this .searchTimeout = searchTimeout ;
281293 }
282294
295+ /**
296+ * Get the timeout for retrying connection to a down host.
297+ *
298+ * @return The delay before connecting again to a down host (ms).
299+ */
300+ public int getHostDownDelay () {
301+ return hostDownDelay ;
302+ }
303+
304+ /**
305+ * Set the timeout for retrying connection to a down host.
306+ *
307+ * @param hostDownDelay The delay before connecting again to a down host (ms).
308+ */
309+ public void setHostDownDelay (int hostDownDelay ) {
310+ checkTimeout (hostDownDelay );
311+ this .hostDownDelay = hostDownDelay ;
312+ }
313+
283314 /**
284315 * Add a software library to the list of user agents.
285316 *
@@ -333,6 +364,14 @@ private void updateUserAgents() {
333364 userAgentRaw = s .toString ();
334365 }
335366
367+ private List <String > getReadHostsThatAreUp () {
368+ return hostsThatAreUp (readHosts );
369+ }
370+
371+ private List <String > getWriteHostsThatAreUp () {
372+ return hostsThatAreUp (writeHosts );
373+ }
374+
336375 // ----------------------------------------------------------------------
337376 // Utilities
338377 // ----------------------------------------------------------------------
@@ -345,27 +384,27 @@ private enum Method {
345384 }
346385
347386 protected byte [] getRequestRaw (String url , boolean search ) throws AlgoliaException {
348- return _requestRaw (Method .GET , url , null , readHosts , connectTimeout , search ? searchTimeout : readTimeout );
387+ return _requestRaw (Method .GET , url , null , getReadHostsThatAreUp () , connectTimeout , search ? searchTimeout : readTimeout );
349388 }
350389
351390 protected JSONObject getRequest (String url , boolean search ) throws AlgoliaException {
352- return _request (Method .GET , url , null , readHosts , connectTimeout , search ? searchTimeout : readTimeout );
391+ return _request (Method .GET , url , null , getReadHostsThatAreUp () , connectTimeout , search ? searchTimeout : readTimeout );
353392 }
354393
355394 protected JSONObject deleteRequest (String url ) throws AlgoliaException {
356- return _request (Method .DELETE , url , null , writeHosts , connectTimeout , readTimeout );
395+ return _request (Method .DELETE , url , null , getWriteHostsThatAreUp () , connectTimeout , readTimeout );
357396 }
358397
359398 protected JSONObject postRequest (String url , String obj , boolean readOperation ) throws AlgoliaException {
360- return _request (Method .POST , url , obj , (readOperation ? readHosts : writeHosts ), connectTimeout , (readOperation ? searchTimeout : readTimeout ));
399+ return _request (Method .POST , url , obj , (readOperation ? getReadHostsThatAreUp () : getWriteHostsThatAreUp () ), connectTimeout , (readOperation ? searchTimeout : readTimeout ));
361400 }
362401
363402 protected byte [] postRequestRaw (String url , String obj , boolean readOperation ) throws AlgoliaException {
364- return _requestRaw (Method .POST , url , obj , (readOperation ? readHosts : writeHosts ), connectTimeout , (readOperation ? searchTimeout : readTimeout ));
403+ return _requestRaw (Method .POST , url , obj , (readOperation ? getReadHostsThatAreUp () : getWriteHostsThatAreUp () ), connectTimeout , (readOperation ? searchTimeout : readTimeout ));
365404 }
366405
367406 protected JSONObject putRequest (String url , String obj ) throws AlgoliaException {
368- return _request (Method .PUT , url , obj , writeHosts , connectTimeout , readTimeout );
407+ return _request (Method .PUT , url , obj , getWriteHostsThatAreUp () , connectTimeout , readTimeout );
369408 }
370409
371410 /**
@@ -526,6 +565,7 @@ private byte[] _requestRaw(Method m, String url, String json, List<String> hosts
526565 if (stream == null ) {
527566 throw new IOException (String .format ("Null stream when reading connection (status %d)" , code ));
528567 }
568+ hostStatuses .put (host , new HostStatus (true ));
529569
530570 final byte [] rawResponse ;
531571 String encoding = hostConnection .getContentEncoding ();
@@ -556,8 +596,8 @@ private byte[] _requestRaw(Method m, String url, String json, List<String> hosts
556596 catch (UnsupportedEncodingException e ) { // fatal
557597 consumeQuietly (hostConnection );
558598 throw new AlgoliaException ("Invalid encoding returned by server" , e );
559- }
560- catch ( IOException e ) { // host error, continue on the next host
599+ } catch ( IOException e ) { // host error, continue on the next host
600+ hostStatuses . put ( host , new HostStatus ( false ));
561601 consumeQuietly (hostConnection );
562602 errors .add (e );
563603 } finally {
@@ -599,6 +639,32 @@ private static void consumeQuietly(final HttpURLConnection connection) {
599639 }
600640 }
601641
642+ private void checkTimeout (int connectTimeout ) {
643+ if (connectTimeout <= 0 ) {
644+ throw new IllegalArgumentException ();
645+ }
646+ }
647+
648+ /**
649+ * Get the hosts that are not considered down in a given list.
650+ * @param hosts a list of hosts whose {@link HostStatus} will be checked.
651+ * @return the hosts considered up, or all hosts if none is known to be reachable.
652+ */
653+ private List <String > hostsThatAreUp (List <String > hosts ) {
654+ List <String > upHosts = new ArrayList <>();
655+ for (String host : hosts ) {
656+ if (isUpOrCouldBeRetried (host )) {
657+ upHosts .add (host );
658+ }
659+ }
660+ return upHosts .isEmpty () ? hosts : upHosts ;
661+ }
662+
663+ boolean isUpOrCouldBeRetried (String host ) {
664+ HostStatus status = hostStatuses .get (host );
665+ return status == null || status .isUp || new Date ().getTime () - status .lastTryTimestamp >= hostDownDelay ;
666+ }
667+
602668 // ----------------------------------------------------------------------
603669 // Utils
604670 // ----------------------------------------------------------------------
0 commit comments