Skip to content

Commit

Permalink
Merge pull request #554 from mziccard/refactor-exception
Browse files Browse the repository at this point in the history
Refactor service exceptions
  • Loading branch information
mziccard committed Jan 21, 2016
2 parents 397b2c1 + 3c81f16 commit add5924
Show file tree
Hide file tree
Showing 28 changed files with 890 additions and 532 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ static Entity fromPb(Access access) {
}
// Unreachable
throw new BigQueryException(BigQueryException.UNKNOWN_CODE,
"Unrecognized access configuration", false);
"Unrecognized access configuration");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@

package com.google.gcloud.bigquery;

import com.google.common.collect.ImmutableSet;
import com.google.gcloud.BaseServiceException;
import com.google.gcloud.RetryHelper.RetryHelperException;
import com.google.gcloud.RetryHelper.RetryInterruptedException;

import java.io.IOException;
import java.util.Set;

/**
* BigQuery service exception.
*
Expand All @@ -28,20 +32,30 @@
*/
public class BigQueryException extends BaseServiceException {

private static final long serialVersionUID = -5504832700512784654L;
public static final int UNKNOWN_CODE = -1;
// see: https://cloud.google.com/bigquery/troubleshooting-errors
private static final Set<Error> RETRYABLE_ERRORS = ImmutableSet.of(
new Error(500, null),
new Error(502, null),
new Error(503, null),
new Error(504, null));
private static final long serialVersionUID = -5006625989225438209L;

private final BigQueryError error;

public BigQueryException(int code, String message, boolean retryable) {
this(code, message, retryable, null);
public BigQueryException(int code, String message) {
this(code, message, null);
}

public BigQueryException(int code, String message, boolean retryable, BigQueryError error) {
super(code, message, retryable);
public BigQueryException(int code, String message, BigQueryError error) {
super(code, message, error != null ? error.reason() : null, true);
this.error = error;
}

public BigQueryException(IOException exception) {
super(exception, true);
this.error = null;
}

/**
* Returns the {@link BigQueryError} that caused this exception. Returns {@code null} if none
* exists.
Expand All @@ -50,20 +64,20 @@ public BigQueryError error() {
return error;
}

@Override
protected Set<Error> retryableErrors() {
return RETRYABLE_ERRORS;
}

/**
* Translate RetryHelperException to the BigQueryException that caused the error. This method will
* always throw an exception.
*
* @throws BigQueryException when {@code ex} was caused by a {@code BigQueryException}
* @throws RetryInterruptedException when {@code ex} is a {@code RetryInterruptedException}
*/
static BigQueryException translateAndThrow(RetryHelperException ex) {
if (ex.getCause() instanceof BigQueryException) {
throw (BigQueryException) ex.getCause();
}
if (ex instanceof RetryInterruptedException) {
RetryInterruptedException.propagate();
}
throw new BigQueryException(UNKNOWN_CODE, ex.getMessage(), false);
static BaseServiceException translateAndThrow(RetryHelperException ex) {
BaseServiceException.translateAndPropagateIfPossible(ex);
throw new BigQueryException(UNKNOWN_CODE, ex.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gcloud.BaseService;
import com.google.gcloud.ExceptionHandler;
import com.google.gcloud.ExceptionHandler.Interceptor;
import com.google.gcloud.Page;
import com.google.gcloud.PageImpl;
import com.google.gcloud.PageImpl.NextPageFetcher;
Expand All @@ -49,27 +47,6 @@

final class BigQueryImpl extends BaseService<BigQueryOptions> implements BigQuery {

private static final Interceptor EXCEPTION_HANDLER_INTERCEPTOR = new Interceptor() {

private static final long serialVersionUID = -7478333733015750774L;

@Override
public RetryResult afterEval(Exception exception, RetryResult retryResult) {
return Interceptor.RetryResult.CONTINUE_EVALUATION;
}

@Override
public RetryResult beforeEval(Exception exception) {
if (exception instanceof BigQueryException) {
boolean retriable = ((BigQueryException) exception).retryable();
return retriable ? Interceptor.RetryResult.RETRY : Interceptor.RetryResult.NO_RETRY;
}
return Interceptor.RetryResult.CONTINUE_EVALUATION;
}
};
static final ExceptionHandler EXCEPTION_HANDLER = ExceptionHandler.builder()
.abortOn(RuntimeException.class).interceptor(EXCEPTION_HANDLER_INTERCEPTOR).build();

private static class DatasetPageFetcher implements NextPageFetcher<DatasetInfo> {

private static final long serialVersionUID = -3057564042439021278L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import static java.net.HttpURLConnection.HTTP_OK;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
Expand Down Expand Up @@ -58,24 +57,19 @@
import com.google.api.services.bigquery.model.TableRow;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

import com.google.gcloud.bigquery.BigQueryError;
import com.google.gcloud.bigquery.BigQueryException;
import com.google.gcloud.bigquery.BigQueryOptions;

import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class DefaultBigQueryRpc implements BigQueryRpc {

public static final String DEFAULT_PROJECTION = "full";
// see: https://cloud.google.com/bigquery/troubleshooting-errors
private static final Set<Integer> RETRYABLE_CODES = ImmutableSet.of(500, 502, 503, 504);
private static final String BASE_RESUMABLE_URI =
"https://www.googleapis.com/upload/bigquery/v2/projects/";
// see: https://cloud.google.com/bigquery/loading-data-post-request#resume-upload
Expand All @@ -94,28 +88,7 @@ public DefaultBigQueryRpc(BigQueryOptions options) {
}

private static BigQueryException translate(IOException exception) {
BigQueryException translated;
if (exception instanceof GoogleJsonResponseException
&& ((GoogleJsonResponseException) exception).getDetails() != null) {
translated = translate(((GoogleJsonResponseException) exception).getDetails());
} else {
translated =
new BigQueryException(BigQueryException.UNKNOWN_CODE, exception.getMessage(), false);
}
translated.initCause(exception);
return translated;
}

private static BigQueryException translate(GoogleJsonError exception) {
boolean retryable = RETRYABLE_CODES.contains(exception.getCode());
BigQueryError bigqueryError = null;
if (exception.getErrors() != null && !exception.getErrors().isEmpty()) {
GoogleJsonError.ErrorInfo error = exception.getErrors().get(0);
bigqueryError = new BigQueryError(error.getReason(), error.getLocation(), error.getMessage(),
(String) error.get("debugInfo"));
}
return new BigQueryException(exception.getCode(), exception.getMessage(), retryable,
bigqueryError);
return new BigQueryException(exception);
}

@Override
Expand Down Expand Up @@ -489,10 +462,7 @@ public void write(String uploadId, byte[] toWrite, int toWriteOffset, long destO
if (exception != null) {
throw exception;
}
GoogleJsonError error = new GoogleJsonError();
error.setCode(code);
error.setMessage(message);
throw translate(error);
throw new BigQueryException(code, message);
}
} catch (IOException ex) {
throw translate(ex);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.gcloud.bigquery;

import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import com.google.gcloud.BaseServiceException;
import com.google.gcloud.RetryHelper.RetryHelperException;

import org.junit.Test;

import java.io.IOException;
import java.net.SocketTimeoutException;

public class BigQueryExceptionTest {

@Test
public void testBigqueryException() {
BigQueryException exception = new BigQueryException(500, "message");
assertEquals(500, exception.code());
assertEquals("message", exception.getMessage());
assertNull(exception.reason());
assertNull(exception.error());
assertTrue(exception.retryable());
assertTrue(exception.idempotent());

exception = new BigQueryException(502, "message");
assertEquals(502, exception.code());
assertEquals("message", exception.getMessage());
assertNull(exception.reason());
assertNull(exception.error());
assertTrue(exception.retryable());
assertTrue(exception.idempotent());

exception = new BigQueryException(503, "message");
assertEquals(503, exception.code());
assertEquals("message", exception.getMessage());
assertNull(exception.reason());
assertNull(exception.error());
assertTrue(exception.retryable());
assertTrue(exception.idempotent());

exception = new BigQueryException(504, "message");
assertEquals(504, exception.code());
assertEquals("message", exception.getMessage());
assertNull(exception.reason());
assertNull(exception.error());
assertTrue(exception.retryable());
assertTrue(exception.idempotent());

exception = new BigQueryException(400, "message");
assertEquals(400, exception.code());
assertEquals("message", exception.getMessage());
assertNull(exception.reason());
assertNull(exception.error());
assertFalse(exception.retryable());
assertTrue(exception.idempotent());

BigQueryError error = new BigQueryError("reason", null, null);
exception = new BigQueryException(504, "message", error);
assertEquals(504, exception.code());
assertEquals("message", exception.getMessage());
assertEquals("reason", exception.reason());
assertEquals(error, exception.error());
assertTrue(exception.retryable());
assertTrue(exception.idempotent());

IOException cause = new SocketTimeoutException();
exception = new BigQueryException(cause);
assertNull(exception.reason());
assertNull(exception.getMessage());
assertTrue(exception.retryable());
assertTrue(exception.idempotent());
assertEquals(cause, exception.getCause());
}

@Test
public void testTranslateAndThrow() throws Exception {
BigQueryException cause = new BigQueryException(503, "message");
RetryHelperException exceptionMock = createMock(RetryHelperException.class);
expect(exceptionMock.getCause()).andReturn(cause).times(2);
replay(exceptionMock);
try {
BigQueryException.translateAndThrow(exceptionMock);
} catch (BaseServiceException ex) {
assertEquals(503, ex.code());
assertEquals("message", ex.getMessage());
assertTrue(ex.retryable());
assertTrue(ex.idempotent());
} finally {
verify(exceptionMock);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ public void testWriter() {
@Test
public void testRetryableException() {
EasyMock.expect(bigqueryRpcMock.getDataset(DATASET, EMPTY_RPC_OPTIONS))
.andThrow(new BigQueryException(500, "InternalError", true))
.andThrow(new BigQueryException(500, "InternalError"))
.andReturn(DATASET_INFO_WITH_PROJECT.toPb());
EasyMock.replay(bigqueryRpcMock);
bigquery = options.toBuilder().retryParams(RetryParams.defaultInstance()).build().service();
Expand All @@ -1034,7 +1034,7 @@ public void testRetryableException() {
public void testNonRetryableException() {
String exceptionMessage = "Not Implemented";
EasyMock.expect(bigqueryRpcMock.getDataset(DATASET, EMPTY_RPC_OPTIONS))
.andThrow(new BigQueryException(501, exceptionMessage, false));
.andThrow(new BigQueryException(501, exceptionMessage));
EasyMock.replay(bigqueryRpcMock);
bigquery = options.toBuilder().retryParams(RetryParams.defaultInstance()).build().service();
thrown.expect(BigQueryException.class);
Expand Down
25 changes: 25 additions & 0 deletions gcloud-java-core/src/main/java/com/google/gcloud/BaseService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.google.gcloud;

import com.google.gcloud.ExceptionHandler.Interceptor;

/**
* Base class for service objects.
*
Expand All @@ -24,6 +26,29 @@
public abstract class BaseService<OptionsT extends ServiceOptions<?, ?, OptionsT>>
implements Service<OptionsT> {

public static final Interceptor EXCEPTION_HANDLER_INTERCEPTOR = new Interceptor() {

private static final long serialVersionUID = -8429573486870467828L;

@Override
public RetryResult afterEval(Exception exception, RetryResult retryResult) {
return Interceptor.RetryResult.CONTINUE_EVALUATION;
}

@Override
public RetryResult beforeEval(Exception exception) {
if (exception instanceof BaseServiceException) {
boolean retriable = ((BaseServiceException) exception).retryable();
return retriable ? Interceptor.RetryResult.RETRY : Interceptor.RetryResult.NO_RETRY;
}
return Interceptor.RetryResult.CONTINUE_EVALUATION;
}
};
public static final ExceptionHandler EXCEPTION_HANDLER = ExceptionHandler.builder()
.abortOn(RuntimeException.class)
.interceptor(EXCEPTION_HANDLER_INTERCEPTOR)
.build();

private final OptionsT options;

protected BaseService(OptionsT options) {
Expand Down
Loading

0 comments on commit add5924

Please sign in to comment.