Skip to content
Merged
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
13 changes: 10 additions & 3 deletions src/main/java/com/google/firebase/FirebaseException.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,27 @@ public FirebaseException(@NonNull String detailMessage) {

@Deprecated
public FirebaseException(@NonNull String detailMessage, Throwable cause) {
this(ErrorCode.UNKNOWN, detailMessage, null, cause);
this(ErrorCode.UNKNOWN, detailMessage, cause, null);
}

public FirebaseException(
@NonNull ErrorCode errorCode,
@NonNull String message,
@Nullable IncomingHttpResponse httpResponse,
@Nullable Throwable cause) {
@Nullable Throwable cause,
@Nullable IncomingHttpResponse httpResponse) {
super(message, cause);
checkArgument(!Strings.isNullOrEmpty(message), "Message must not be null or empty");
this.errorCode = checkNotNull(errorCode, "ErrorCode must not be null");
this.httpResponse = httpResponse;
}

public FirebaseException(
@NonNull ErrorCode errorCode,
@NonNull String message,
@Nullable Throwable cause) {
this(errorCode, message, cause, null);
}

/**
* Returns the platform-wide error code associated with this exception.
*
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/google/firebase/IncomingHttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ public IncomingHttpResponse(HttpResponseException e, HttpRequest request) {
this(e, new OutgoingHttpRequest(request));
}

IncomingHttpResponse(HttpResponseException e, OutgoingHttpRequest request) {
/**
* Creates an IncomingHttpResponse from an HTTP error response.
*
* @param e HttpResponseException representing the HTTP error response.
* @param request The OutgoingHttpRequest that resulted in the error.
*/
public IncomingHttpResponse(HttpResponseException e, OutgoingHttpRequest request) {
checkNotNull(e, "exception must not be null");
this.statusCode = e.getStatusCode();
this.content = e.getContent();
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/google/firebase/OutgoingHttpRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ public final class OutgoingHttpRequest {
private final HttpContent content;
private final Map<String, Object> headers;

OutgoingHttpRequest(String method, String url) {
/**
* Creates an OutgoingHttpRequest from the HTTP method and URL.
*
* @param method HTTP method name.
* @param url Target HTTP URL of the request.
*/
public OutgoingHttpRequest(String method, String url) {
checkArgument(!Strings.isNullOrEmpty(method), "method must not be null or empty");
checkArgument(!Strings.isNullOrEmpty(url), "url must not be empty");
this.method = method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@

package com.google.firebase.internal;

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

import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseException;
import com.google.firebase.IncomingHttpResponse;
import java.io.IOException;
import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* An abstract HttpErrorHandler implementation that maps HTTP status codes to Firebase error codes.
* Also provides reasonable default implementations to other error handler methods in the
* HttpErrorHandler interface.
*/
public abstract class AbstractHttpErrorHandler<T extends FirebaseException>
implements HttpErrorHandler<T> {
Expand All @@ -49,59 +53,102 @@ public abstract class AbstractHttpErrorHandler<T extends FirebaseException>
@Override
public final T handleHttpResponseException(
HttpResponseException e, IncomingHttpResponse response) {
ErrorParams params = getErrorParams(e, response);
return this.createException(params);
FirebaseException base = this.httpResponseErrorToBaseException(e, response);
return this.createException(base);
}

protected ErrorParams getErrorParams(HttpResponseException e, IncomingHttpResponse response) {
@Override
public final T handleIOException(IOException e) {
FirebaseException base = this.ioErrorToBaseException(e);
return this.createException(base);
}

@Override
public final T handleParseException(IOException e, IncomingHttpResponse response) {
FirebaseException base = this.parseErrorToBaseException(e, response);
return this.createException(base);
}

/**
* Creates a FirebaseException from the given HTTP response error. Error code is determined from
* the HTTP status code of the response. Error message includes both the status code and full
* response payload to aid in debugging.
*
* @param e HTTP response exception.
* @param response Incoming HTTP response.
* @return A FirebaseException instance.
*/
protected FirebaseException httpResponseErrorToBaseException(
HttpResponseException e, IncomingHttpResponse response) {
ErrorCode code = HTTP_ERROR_CODES.get(e.getStatusCode());
if (code == null) {
code = ErrorCode.UNKNOWN;
}

String message = String.format("Unexpected HTTP response with status: %d\n%s",
e.getStatusCode(), e.getContent());
return new ErrorParams(code, message, e, response);
return new FirebaseException(code, message, e, response);
}

protected abstract T createException(ErrorParams params);

public static final class ErrorParams {

private final ErrorCode errorCode;
private final String message;
private final HttpResponseException exception;
private final IncomingHttpResponse response;

public ErrorParams(
ErrorCode errorCode,
String message,
@Nullable HttpResponseException e,
@Nullable IncomingHttpResponse response) {

this.errorCode = checkNotNull(errorCode, "errorCode must not be null");
checkArgument(!Strings.isNullOrEmpty(message), "message must not be null or empty");
this.message = message;
this.exception = e;
this.response = response;
/**
* Creates a FirebaseException from the given IOException. If IOException resulted from a socket
* timeout, sets the error code DEADLINE_EXCEEDED. If the IOException resulted from a network
* outage or other connectivity issue, sets the error code to UNAVAILABLE. In all other cases sets
* the error code to UNKNOWN.
*
* @param e IOException to create the new exception from.
* @return A FirebaseException instance.
*/
protected FirebaseException ioErrorToBaseException(IOException e) {
ErrorCode code = ErrorCode.UNKNOWN;
String message = "Unknown error while making a remote service call" ;
if (isInstance(e, SocketTimeoutException.class)) {
code = ErrorCode.DEADLINE_EXCEEDED;
message = "Timed out while making an API call";
}

public ErrorCode getErrorCode() {
return errorCode;
if (isInstance(e, UnknownHostException.class) || isInstance(e, NoRouteToHostException.class)) {
code = ErrorCode.UNAVAILABLE;
message = "Failed to establish a connection";
}

public String getMessage() {
return message;
}
return new FirebaseException(code, message + ": " + e.getMessage(), e);
}

@Nullable
public HttpResponseException getException() {
return exception;
}
protected FirebaseException parseErrorToBaseException(
IOException e, IncomingHttpResponse response) {
return new FirebaseException(
ErrorCode.UNKNOWN, "Error while parsing HTTP response: " + e.getMessage(), e, response);
}

@Nullable
public IncomingHttpResponse getResponse() {
return response;
/**
* Converts the given base FirebaseException to a more specific exception type. The base exception
* is guaranteed to have an error code, a message and a cause. But the HTTP response is only set
* if the exception occurred after receiving a response from a remote server.
*
* @param base A FirebaseException.
* @return A more specific exception created from the base.
*/
protected abstract T createException(FirebaseException base);

/**
* Checks if the given exception stack t contains an instance of type.
*/
private <U> boolean isInstance(IOException t, Class<U> type) {
Throwable current = t;
Set<Throwable> chain = new HashSet<>();
while (current != null) {
if (!chain.add(current)) {
break;
}

if (type.isInstance(current)) {
return true;
}

current = current.getCause();
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,23 @@ public AbstractPlatformErrorHandler(JsonFactory jsonFactory) {
}

@Override
protected final ErrorParams getErrorParams(
protected final FirebaseException httpResponseErrorToBaseException(
HttpResponseException e, IncomingHttpResponse response) {
ErrorParams defaults = super.getErrorParams(e, response);
FirebaseException base = super.httpResponseErrorToBaseException(e, response);
PlatformErrorResponse parsedError = this.parseErrorResponse(e.getContent());

ErrorCode code = defaults.getErrorCode();
ErrorCode code = base.getErrorCodeNew();
String status = parsedError.getStatus();
if (!Strings.isNullOrEmpty(status)) {
code = Enum.valueOf(ErrorCode.class, parsedError.getStatus());
}

String message = parsedError.getMessage();
if (Strings.isNullOrEmpty(message)) {
message = defaults.getMessage();
message = base.getMessage();
}

return new ErrorParams(code, message, e, response);
return new FirebaseException(code, message, e, response);
}

private PlatformErrorResponse parseErrorResponse(String content) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class ApiClientUtils {
.setMaxIntervalMillis(60 * 1000)
.build();

private ApiClientUtils() { }

/**
* Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and
* automatic retries.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.JsonParser;
import com.google.common.io.CharStreams;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseException;
import com.google.firebase.IncomingHttpResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
Expand All @@ -42,14 +42,6 @@ public final class ErrorHandlingHttpClient<T extends FirebaseException> {
private final JsonFactory jsonFactory;
private final HttpErrorHandler<T> errorHandler;

public ErrorHandlingHttpClient(
FirebaseApp app, HttpErrorHandler<T> errorHandler, RetryConfig retryConfig) {
this(
ApiClientUtils.newAuthorizedRequestFactory(app, retryConfig),
app.getOptions().getJsonFactory(),
errorHandler);
}

public ErrorHandlingHttpClient(
HttpRequestFactory requestFactory,
JsonFactory jsonFactory,
Expand All @@ -74,16 +66,36 @@ public <V> V sendAndParse(HttpRequestInfo requestInfo, Class<V> responseType) th
return parse(response, responseType);
}

private IncomingHttpResponse send(HttpRequestInfo requestInfo) throws T {
/**
* Sends the given HTTP request to the target endpoint, and parses the response while handling
* any errors that may occur along the way. This method can be used when the response should
* be parsed into an instance of a private or protected class, which cannot be instantiated
* outside the call-site.
*
* @param requestInfo Outgoing request configuration.
* @param destination Object to parse the response into.
* @throws T If any error occurs while making the request.
*/
public void sendAndParse(HttpRequestInfo requestInfo, Object destination) throws T {
IncomingHttpResponse response = send(requestInfo);
parse(response, destination);
}

public IncomingHttpResponse send(HttpRequestInfo requestInfo) throws T {
HttpRequest request = createHttpRequest(requestInfo);

HttpResponse response = null;
try {
response = request.execute();
// Read and buffer the content. Otherwise if a parse error occurs later,
// we lose the content stream.
String content = CharStreams.toString(
new InputStreamReader(response.getContent(), response.getContentCharset()));
String content = null;
InputStream stream = response.getContent();
if (stream != null) {
// Stream is null when the response body is empty (e.g. 204 No Content responses).
content = CharStreams.toString(new InputStreamReader(stream, response.getContentCharset()));
}

return new IncomingHttpResponse(response, content);
} catch (HttpResponseException e) {
throw errorHandler.handleHttpResponseException(e, new IncomingHttpResponse(e, request));
Expand All @@ -103,6 +115,15 @@ private <V> V parse(IncomingHttpResponse response, Class<V> responseType) throws
}
}

private void parse(IncomingHttpResponse response, Object destination) throws T {
try {
JsonParser parser = jsonFactory.createJsonParser(response.getContent());
parser.parse(destination);
} catch (IOException e) {
throw errorHandler.handleParseException(e, response);
}
}

private HttpRequest createHttpRequest(HttpRequestInfo requestInfo) throws T {
try {
return requestInfo.newHttpRequest(requestFactory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@
*/
public class FirebaseMessaging {

static final String INTERNAL_ERROR = "internal-error";

static final String UNKNOWN_ERROR = "unknown-error";

private final FirebaseApp app;
private final Supplier<? extends FirebaseMessagingClient> messagingClient;
private final Supplier<? extends InstanceIdClient> instanceIdClient;
Expand Down
Loading