Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor service exceptions #438

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,80 @@

package com.google.gcloud;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.Set;

/**
* Base class for all service exceptions.
*/
public class BaseServiceException extends RuntimeException {

private static final long serialVersionUID = 5028833760039966178L;
public static final int UNKNOWN_CODE = 0;

private final int code;
private final boolean retryable;
private final boolean idempotent;

public BaseServiceException(IOException exception, boolean idempotent) {
super(exception.getMessage(), exception);
this.code = statusCode(exception);
this.idempotent = idempotent;
this.retryable = idempotent && isRetryable(exception);
}

public BaseServiceException(GoogleJsonError error, boolean idempotent) {
super(error.getMessage());
this.code = error.getCode();
this.idempotent = idempotent;
this.retryable = idempotent && isRetryable(error);
}

public BaseServiceException(int code, String message, boolean retryable) {
public BaseServiceException(int code, String message, boolean retryable, boolean idempotent) {
super(message);
this.code = code;
this.retryable = retryable;
this.idempotent = idempotent;
this.retryable = idempotent && retryable;
}

public BaseServiceException(int code, String message, boolean retryable, Exception cause) {
public BaseServiceException(int code, String message, boolean retryable, boolean idempotent,
Exception cause) {
super(message, cause);
this.code = code;
this.retryable = retryable;
this.idempotent = idempotent;
this.retryable = idempotent && retryable;
}

protected Set<Integer> retryableCodes() {

This comment was marked as spam.

This comment was marked as spam.

return Collections.emptySet();
}

protected int statusCode(IOException exception) {
if (exception instanceof GoogleJsonResponseException) {

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

if (((GoogleJsonResponseException) exception).getDetails() != null) {
return ((GoogleJsonResponseException) exception).getDetails().getCode();
}
}
return UNKNOWN_CODE;
}

protected boolean isRetryable(GoogleJsonError error) {
return error != null && retryableCodes().contains(error.getCode());
}

protected boolean isRetryable(IOException exception) {
if (exception instanceof GoogleJsonResponseException) {
return isRetryable(((GoogleJsonResponseException) exception).getDetails());
}
if (exception instanceof SocketTimeoutException) {
return true;
}
return false;
}

/**
Expand All @@ -51,4 +105,22 @@ public int code() {
public boolean retryable() {
return retryable;
}

/**
* Returns {@code true} when the operation that caused this exception had no side effects.
*/
public boolean idempotent() {
return idempotent;
}

protected static <T extends BaseServiceException> T translateAndThrow(
RetryHelper.RetryHelperException ex) {
if (ex.getCause() instanceof BaseServiceException) {
throw (T) ex.getCause();
}
if (ex instanceof RetryHelper.RetryInterruptedException) {
RetryHelper.RetryInterruptedException.propagate();
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,91 @@

package com.google.gcloud;

import com.google.api.client.googleapis.json.GoogleJsonError;

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 org.junit.Test;

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

/**
* Tests for {@link BaseServiceException}.
*/
public class BaseServiceExceptionTest {

private final int code = 1;
private final String message = "some message";
private final boolean retryable = true;
private static final int CODE = 1;
private static final String MESSAGE = "some message";
private static final boolean RETRYABLE = true;
private static final boolean IDEMPOTENT = true;

@Test
public void testBaseServiceException() {
BaseServiceException serviceException = new BaseServiceException(code, message, retryable);
assertEquals(serviceException.code(), code);
assertEquals(serviceException.getMessage(), message);
assertEquals(serviceException.getCause(), null);
BaseServiceException serviceException = new BaseServiceException(CODE, MESSAGE, RETRYABLE,
IDEMPOTENT);
assertEquals(CODE, serviceException.code());
assertEquals(MESSAGE, serviceException.getMessage());
assertEquals(RETRYABLE, serviceException.retryable());
assertEquals(IDEMPOTENT, serviceException.idempotent());
assertEquals(null, serviceException.getCause());

serviceException = new BaseServiceException(CODE, MESSAGE, RETRYABLE, false);
assertEquals(CODE, serviceException.code());
assertEquals(MESSAGE, serviceException.getMessage());
assertEquals(false, serviceException.retryable());
assertEquals(false, serviceException.idempotent());
assertEquals(null, serviceException.getCause());

Exception cause = new RuntimeException();
serviceException = new BaseServiceException(code, message, retryable, cause);
assertEquals(serviceException.code(), code);
assertEquals(serviceException.getMessage(), message);
assertEquals(serviceException.getCause(), cause);
serviceException = new BaseServiceException(CODE, MESSAGE, RETRYABLE, IDEMPOTENT, cause);
assertEquals(CODE, serviceException.code());
assertEquals(MESSAGE, serviceException.getMessage());
assertEquals(RETRYABLE, serviceException.retryable());
assertEquals(IDEMPOTENT, serviceException.idempotent());
assertEquals(cause, serviceException.getCause());

serviceException = new BaseServiceException(CODE, MESSAGE, RETRYABLE, false, cause);
assertEquals(CODE, serviceException.code());
assertEquals(MESSAGE, serviceException.getMessage());
assertEquals(false, serviceException.retryable());
assertEquals(false, serviceException.idempotent());

IOException exception = new SocketTimeoutException();
serviceException = new BaseServiceException(exception, true);
assertEquals(true, serviceException.retryable());
assertEquals(true, serviceException.idempotent());
assertEquals(exception, serviceException.getCause());

GoogleJsonError error = new GoogleJsonError();
error.setCode(CODE);
error.setMessage(MESSAGE);
serviceException = new BaseServiceException(error, true);
assertEquals(CODE, serviceException.code());
assertEquals(MESSAGE, serviceException.getMessage());
assertEquals(false, serviceException.retryable());
assertEquals(true, serviceException.idempotent());
}

@Test
public void testTranslateAndThrow() throws Exception {
BaseServiceException cause = new BaseServiceException(CODE, MESSAGE, RETRYABLE, IDEMPOTENT);
RetryHelper.RetryHelperException exceptionMock = createMock(RetryHelper.RetryHelperException.class);
expect(exceptionMock.getCause()).andReturn(cause).times(2);
replay(exceptionMock);
try {
BaseServiceException.translateAndThrow(exceptionMock);
} catch (BaseServiceException ex) {
assertEquals(CODE, ex.code());
assertEquals(MESSAGE, ex.getMessage());
assertEquals(RETRYABLE, ex.retryable());
assertEquals(IDEMPOTENT, ex.idempotent());
} finally {
verify(exceptionMock);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.google.gcloud.datastore;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import com.google.api.services.datastore.DatastoreV1;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
Expand Down Expand Up @@ -64,10 +67,8 @@ public final void addWithDeferredIdAllocation(FullEntity<?>... entities) {

private void addInternal(FullEntity<Key> entity) {
Key key = entity.key();
if (toAdd.containsKey(key) || toUpdate.containsKey(key) || toPut.containsKey(key)) {
throw newInvalidRequest("Entity with the key %s was already added or updated in this %s",
entity.key(), name);
}
checkArgument(!toAdd.containsKey(key) && !toUpdate.containsKey(key) && !toPut.containsKey(key),

This comment was marked as spam.

"Entity with the key %s was already added or updated in this %s", entity.key(), name);
if (toDelete.remove(key)) {
toPut.put(key, entity);
} else {
Expand Down Expand Up @@ -120,10 +121,8 @@ public final void update(Entity... entities) {
validateActive();
for (Entity entity : entities) {
Key key = entity.key();
if (toDelete.contains(key)) {
throw newInvalidRequest("Entity with the key %s was already deleted in this %s",
entity.key(), name);
}
checkArgument(!toDelete.contains(key),
"Entity with the key %s was already deleted in this %s", entity.key(), name);
if (toAdd.remove(key) != null || toPut.containsKey(key)) {
toPut.put(key, entity);
} else {
Expand Down Expand Up @@ -190,13 +189,7 @@ protected void deactivate() {
}

protected void validateActive() {
if (!active) {
throw newInvalidRequest("%s is no longer active", name);
}
}

protected DatastoreException newInvalidRequest(String msg, Object... params) {
return DatastoreException.throwInvalidRequest(String.format(msg, params));
checkState(active, "%s is no longer active", name);
}

protected DatastoreV1.Mutation.Builder toMutationPb() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.gcloud.datastore;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gcloud.datastore.BlobValue.of;
import static com.google.gcloud.datastore.BooleanValue.of;
import static com.google.gcloud.datastore.DateTimeValue.of;
Expand Down Expand Up @@ -248,9 +249,7 @@ public boolean contains(String name) {
public <V extends Value<?>> V getValue(String name) {
@SuppressWarnings("unchecked")
V property = (V) properties.get(name);
if (property == null) {
throw DatastoreException.throwInvalidRequest("No such property %s", name);
}
checkArgument(property != null, "No such property %s", name);
return property;
}

Expand Down
Loading