
com.couchbase.client.java.view.ViewRetryHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-client Show documentation
Show all versions of java-client Show documentation
The official Couchbase Java SDK
/**
* Copyright (C) 2014 Couchbase, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
* IN THE SOFTWARE.
*/
package com.couchbase.client.java.view;
import com.couchbase.client.core.CouchbaseException;
import com.couchbase.client.core.RequestCancelledException;
import com.couchbase.client.core.logging.CouchbaseLogger;
import com.couchbase.client.core.logging.CouchbaseLoggerFactory;
import com.couchbase.client.core.message.view.ViewQueryResponse;
import rx.Observable;
import rx.functions.Func1;
import java.util.concurrent.TimeUnit;
/**
* Generic View retry handler based on response code and value inspection.
*
* @author Michael Nitschinger
* @since 2.0.2
*/
public class ViewRetryHandler {
private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(ViewRetryHandler.class);
private static final ShouldRetryViewRequestException SHOULD_RETRY = new ShouldRetryViewRequestException();
static {
SHOULD_RETRY.setStackTrace(new StackTraceElement[]{});
}
/**
* Takes a {@link ViewQueryResponse}, verifies their status based on fixed criteria and resubscribes if needed.
*
* If it needs to be retried, the resubscription will happen after 10 milliseconds to give the underlying code
* some time to recover.
*
* @param input the original response.
* @return the good response which can be parsed, or a failing observable.
*/
public static Observable retryOnCondition(final Observable input) {
return input
.flatMap(new Func1>() {
@Override
public Observable call(final ViewQueryResponse response) {
return passThroughOrThrow(response);
}
})
.retryWhen(new Func1, Observable>>() {
@Override
public Observable> call(Observable extends Throwable> observable) {
return observable
.flatMap(new Func1>() {
@Override
public Observable> call(Throwable throwable) {
if (throwable instanceof ShouldRetryViewRequestException
|| throwable instanceof RequestCancelledException) {
return Observable.timer(10, TimeUnit.MILLISECONDS);
} else {
return Observable.error(throwable);
}
}
});
}
})
.last();
}
/**
* Helper method which decides if the response is good to pass through or needs to be retried.
*
* @param response the response to look at.
* @return the {@link ViewQueryResponse} if it can pass through or an error if it needs to be retried.
*/
private static Observable passThroughOrThrow(final ViewQueryResponse response) {
final int responseCode = response.responseCode();
if (responseCode == 200) {
return Observable.just(response);
}
return response
.error()
.map(new Func1() {
@Override
public ViewQueryResponse call(String error) {
if (shouldRetry(responseCode, error)) {
throw SHOULD_RETRY;
}
return response;
}
})
.singleOrDefault(response);
}
/**
* Analyses status codes and checks if a retry needs to happen.
*
* Some status codes are ambiguous, so their contents are inspected further.
*
* @param status the status code.
* @param content the error body from the response.
* @return true if retry is needed, false otherwise.
*/
private static boolean shouldRetry(final int status, final String content) {
switch (status) {
case 200:
return false;
case 404:
return analyse404Response(content);
case 500:
return analyse500Response(content);
case 300:
case 301:
case 302:
case 303:
case 307:
case 401:
case 408:
case 409:
case 412:
case 416:
case 417:
case 501:
case 502:
case 503:
case 504:
return true;
default:
LOGGER.info("Received a View HTTP response code ({}) I did not expect, not retrying.", status);
return false;
}
}
/**
* Analyses the content of a 404 response to see if it is legible for retry.
*
* If the content contains ""reason":"missing"", it is a clear indication that the responding node
* is unprovisioned and therefore it should be retried. All other cases indicate a provisioned node,
* but the design document/view is not found, which should not be retried.
*
* @param content the parsed error content.
* @return true if it needs to be retried, false otherwise.
*/
private static boolean analyse404Response(final String content) {
if (content.contains("\"reason\":\"missing\"")) {
return true;
}
LOGGER.debug("Design document not found, error is {}", content);
return false;
}
/**
* Analyses the content of a 500 response to see if it is legible for retry.
*
* @param content the parsed error content.
* @return true if it needs to be retried, false otherwise.
*/
private static boolean analyse500Response(final String content) {
if (content.contains("error") && content.contains("{not_found, missing_named_view}")) {
LOGGER.debug("Design document not found, error is {}", content);
return false;
}
return true;
}
/**
* Exception type indicating a view needs to be retried.
*/
private static class ShouldRetryViewRequestException extends CouchbaseException { }
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy