Skip to content

Commit 6d67057

Browse files
author
Stephen Cobbe
committed
Added global response handlers.
1 parent 877ed68 commit 6d67057

File tree

15 files changed

+264
-14
lines changed

15 files changed

+264
-14
lines changed

examples/global-callbacks/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/build/
2+
/target/

examples/global-callbacks/ReadMe.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Global Handlers Example
2+
3+
This repo contains an advanced use-case example (global route error and network error response callbacks).
4+
5+
Client can supply a callback factory, which allows for consistent, global handling of route-specific error types, as well as general network errors.
6+
7+
Normally, error handling is done on a request-by-request basis. However, it is convenient to handle some error behavior consistently, regardless of the request or the endpoint.
8+
9+
For implementing global error handling for general networking errors like an auth error, `getNetworkErrorCallback` should be implemented. For implementing global error handling for route-specific errors, one of the `getRouteErrorCallback` should be implemented.
10+
11+
To run this example, please replace `"<ACCESS TOKEN>"` in `Main.java` with your access token. See the online tutorial for more details on how to generate an access token.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
description = 'Global Callbacks Example (Dropbox Core API SDK)'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dropbox.core.examples.global_callbacks;
2+
3+
import com.dropbox.core.v2.callbacks.DbxGlobalCallbackFactory;
4+
import com.dropbox.core.v2.callbacks.DbxRouteErrorCallback;
5+
import com.dropbox.core.v2.callbacks.DbxNetworkErrorCallback;
6+
import com.dropbox.core.v2.files.ListFolderError;
7+
import com.dropbox.core.v2.files.LookupError;
8+
9+
public class DbxExampleGlobalCallbackFactory implements DbxGlobalCallbackFactory {
10+
@Override
11+
public <T> DbxRouteErrorCallback<T> createRouteErrorCallback(T routeError) {
12+
if (routeError instanceof ListFolderError) {
13+
return new DbxExampleListFolderErrorCallback<T>();
14+
} else if (routeError instanceof LookupError) {
15+
return new DbxExampleLookupErrorCallback<T>();
16+
}
17+
18+
return null;
19+
}
20+
21+
@Override
22+
public DbxExampleNetworkErrorCallback createNetworkErrorCallback() {
23+
return new DbxExampleNetworkErrorCallback();
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.dropbox.core.examples.global_callbacks;
2+
3+
import com.dropbox.core.v2.callbacks.DbxRouteErrorCallback;
4+
import com.dropbox.core.v2.files.ListFolderError;
5+
6+
public class DbxExampleListFolderErrorCallback<T> extends DbxRouteErrorCallback<T> {
7+
@Override
8+
public void run() {
9+
System.out.println("GLOBAL ROUTE ERROR HANDLER (ListFolderError): " + this.getRouteError() + "\n");
10+
// do some work
11+
}
12+
}
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.dropbox.core.examples.global_callbacks;
2+
3+
import com.dropbox.core.v2.callbacks.DbxRouteErrorCallback;
4+
import com.dropbox.core.v2.files.LookupError;
5+
6+
public class DbxExampleLookupErrorCallback<T> extends DbxRouteErrorCallback<T> {
7+
@Override
8+
public void run() {
9+
System.out.println("GLOBAL ROUTE ERROR HANDLER (LookupError): " + this.getRouteError() + "\n");
10+
// do some work
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.dropbox.core.examples.global_callbacks;
2+
3+
import com.dropbox.core.v2.callbacks.DbxNetworkErrorCallback;
4+
import com.dropbox.core.InvalidAccessTokenException;
5+
6+
public class DbxExampleNetworkErrorCallback extends DbxNetworkErrorCallback {
7+
@Override
8+
public void run() {
9+
if (this.getNetworkError() instanceof InvalidAccessTokenException) {
10+
System.out.println("GLOBAL NETWORK ERROR HANDLER: " + this.getNetworkError() + "\n");
11+
// do some work
12+
}
13+
}
14+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.dropbox.core.examples.global_callbacks;
2+
3+
import com.dropbox.core.DbxException;
4+
import com.dropbox.core.DbxRequestConfig;
5+
import com.dropbox.core.DbxRequestUtil;
6+
import com.dropbox.core.v2.callbacks.DbxGlobalCallbackFactory;
7+
import com.dropbox.core.v2.callbacks.DbxRouteErrorCallback;
8+
import com.dropbox.core.v2.callbacks.DbxNetworkErrorCallback;
9+
import com.dropbox.core.v2.DbxClientV2;
10+
import com.dropbox.core.v2.files.FileMetadata;
11+
import com.dropbox.core.v2.files.ListFolderError;
12+
import com.dropbox.core.v2.files.ListFolderErrorException;
13+
import com.dropbox.core.v2.files.ListFolderResult;
14+
import com.dropbox.core.v2.files.Metadata;
15+
import com.dropbox.core.v2.users.FullAccount;
16+
17+
import java.util.List;
18+
19+
import java.io.FileInputStream;
20+
import java.io.InputStream;
21+
import java.io.IOException;
22+
23+
public class Main {
24+
private static final String ACCESS_TOKEN = "<ACCESS TOKEN>";
25+
26+
public static void main(String args[]) throws DbxException, IOException {
27+
// Create Dropbox client
28+
DbxRequestConfig config = new DbxRequestConfig("dropbox/java-tutorial");
29+
DbxClientV2 client = new DbxClientV2(config, ACCESS_TOKEN);
30+
31+
// Instantiate factory and set shared factory
32+
DbxRequestUtil.sharedCallbackFactory = new DbxExampleGlobalCallbackFactory();
33+
34+
try {
35+
// Get files and folder metadata from Dropbox root directory
36+
client.files().listFolder("/does/not/exist");
37+
}
38+
catch (ListFolderErrorException ex) {
39+
System.err.println("STANDARD ROUTE ERROR HANDLER: " + ex.errorValue + "\n");
40+
}
41+
catch (DbxException ex) {
42+
System.err.println("STANDARD NETWORK ERROR HANDLER: " + ex + "\n");
43+
}
44+
45+
// try {
46+
// // Get files and folder metadata from Dropbox root directory
47+
// client.auth().tokenRevoke();
48+
// client.files().listFolder("/does/not/exist");
49+
// }
50+
// catch (ListFolderErrorException ex) {
51+
// System.err.println("STANDARD ROUTE ERROR HANDLER2: " + ex.errorValue + "\n");
52+
// }
53+
// catch (DbxException ex) {
54+
// System.err.println("STANDARD NETWORK ERROR HANDLER2: " + ex + "\n");
55+
// }
56+
}
57+
}

examples/settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
rootProject.name = 'examples'
22
include ':account-info'
33
include ':authorize'
4+
include ':global-callbacks'
45
include ':longpoll'
56
include ':tutorial'
67
include ':upgrade-oauth1-token'

examples/tutorial/src/main/java/com/dropbox/core/examples/tutorial/Main.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import com.dropbox.core.DbxException;
44
import com.dropbox.core.DbxRequestConfig;
5+
import com.dropbox.core.v2.callbacks.DbxGlobalCallbackFactory;
6+
import com.dropbox.core.v2.callbacks.DbxRouteErrorCallback;
7+
import com.dropbox.core.v2.callbacks.DbxNetworkErrorCallback;
58
import com.dropbox.core.v2.DbxClientV2;
69
import com.dropbox.core.v2.files.FileMetadata;
710
import com.dropbox.core.v2.files.ListFolderResult;

src/main/java/com/dropbox/core/DbxRequestUtil.java

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.dropbox.core;
22

3-
import java.io.ByteArrayOutputStream;
43
import java.io.IOException;
5-
import java.io.InputStream;
64
import java.io.UnsupportedEncodingException;
7-
import java.lang.reflect.Type;
85
import java.net.URI;
96
import java.net.URISyntaxException;
107
import java.net.URLEncoder;
@@ -15,12 +12,13 @@
1512
import java.util.Map;
1613
import java.util.Random;
1714

18-
import com.dropbox.core.stone.StoneSerializer;
1915
import com.dropbox.core.http.HttpRequestor;
2016
import com.dropbox.core.json.JsonReadException;
2117
import com.dropbox.core.json.JsonReader;
2218
import com.dropbox.core.util.IOUtil;
2319
import com.dropbox.core.util.StringUtil;
20+
import com.dropbox.core.v2.callbacks.DbxGlobalCallbackFactory;
21+
import com.dropbox.core.v2.callbacks.DbxNetworkErrorCallback;
2422

2523
import static com.dropbox.core.util.StringUtil.jq;
2624
import static com.dropbox.core.util.LangUtil.mkAssert;
@@ -30,6 +28,8 @@
3028
public final class DbxRequestUtil {
3129
private static final Random RAND = new Random();
3230

31+
public static DbxGlobalCallbackFactory sharedCallbackFactory;
32+
3333
public static String encodeUrlParam(String s) {
3434
try {
3535
return URLEncoder.encode(s, "UTF-8");
@@ -278,44 +278,63 @@ public static String parseErrorBody(String requestId, int statusCode, byte[] bod
278278

279279
public static DbxException unexpectedStatus(HttpRequestor.Response response)
280280
throws NetworkIOException, BadResponseException {
281+
DbxException networkError;
282+
281283
String requestId = getRequestId(response);
282284
byte[] body = loadErrorBody(response);
283285
String message = parseErrorBody(requestId, response.getStatusCode(), body);
284286

285287
switch (response.getStatusCode()) {
286288
case 400:
287-
return new BadRequestException(requestId, message);
289+
networkError = new BadRequestException(requestId, message);
290+
break;
288291
case 401:
289-
return new InvalidAccessTokenException(requestId, message);
292+
networkError = new InvalidAccessTokenException(requestId, message);
293+
break;
290294
case 429:
291295
try {
292296
int backoffSecs = Integer.parseInt(getFirstHeader(response, "Retry-After"));
293-
return new RateLimitException(requestId, message, backoffSecs, TimeUnit.SECONDS);
297+
networkError = new RateLimitException(requestId, message, backoffSecs, TimeUnit.SECONDS);
298+
break;
294299
} catch (NumberFormatException ex) {
295-
return new BadResponseException(requestId, "Invalid value for HTTP header: \"Retry-After\"");
300+
networkError = new BadResponseException(requestId, "Invalid value for HTTP header: \"Retry-After\"");
301+
break;
296302
}
297303
case 500:
298-
return new ServerException(requestId, message);
304+
networkError = new ServerException(requestId, message);
305+
break;
299306
case 503:
300307
// API v1 may include Retry-After in 503 responses, v2 does not
301308
String retryAfter = getFirstHeaderMaybe(response, "Retry-After");
302309
try {
303310
if (retryAfter != null && !retryAfter.trim().isEmpty()) {
304311
int backoffSecs = Integer.parseInt(retryAfter);
305-
return new RetryException(requestId, message, backoffSecs, TimeUnit.SECONDS);
312+
networkError = new RetryException(requestId, message, backoffSecs, TimeUnit.SECONDS);
313+
break;
306314
} else {
307-
return new RetryException(requestId, message);
315+
networkError = new RetryException(requestId, message);
316+
break;
308317
}
309318
} catch (NumberFormatException ex) {
310-
return new BadResponseException(requestId, "Invalid value for HTTP header: \"Retry-After\"");
319+
networkError = new BadResponseException(requestId, "Invalid value for HTTP header: \"Retry-After\"");
320+
break;
311321
}
312322
default:
313-
return new BadResponseCodeException(
323+
networkError = new BadResponseCodeException(
314324
requestId,
315325
"unexpected HTTP status code: " + response.getStatusCode() + ": " + message,
316326
response.getStatusCode()
317327
);
318328
}
329+
330+
DbxGlobalCallbackFactory factory = DbxRequestUtil.sharedCallbackFactory;
331+
if (factory != null) {
332+
DbxNetworkErrorCallback callback = factory.createNetworkErrorCallback();
333+
callback.setNetworkError(networkError);
334+
callback.run();
335+
}
336+
337+
return networkError;
319338
}
320339

321340
public static <T> T readJsonFromResponse(JsonReader<T> reader, HttpRequestor.Response response)

src/main/java/com/dropbox/core/DbxWrappedException.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package com.dropbox.core;
22

33
import java.io.IOException;
4+
import java.lang.reflect.Field;
5+
import java.lang.reflect.Method;
46

57
import com.fasterxml.jackson.core.JsonParseException;
68

79
import com.dropbox.core.stone.StoneSerializer;
810
import com.dropbox.core.http.HttpRequestor;
11+
import com.dropbox.core.v2.callbacks.DbxGlobalCallbackFactory;
12+
import com.dropbox.core.v2.callbacks.DbxRouteErrorCallback;
913

1014
/**
1115
* For internal use only.
@@ -42,6 +46,42 @@ public static <T> DbxWrappedException fromResponse(StoneSerializer<T> errSeriali
4246
ApiErrorResponse<T> apiResponse = new ApiErrorResponse.Serializer<T>(errSerializer)
4347
.deserialize(response.getBody());
4448

45-
return new DbxWrappedException(apiResponse.getError(), requestId, apiResponse.getUserMessage());
49+
T routeError = apiResponse.getError();
50+
51+
DbxGlobalCallbackFactory factory = DbxRequestUtil.sharedCallbackFactory;
52+
DbxWrappedException.executeBlockForObject(factory, routeError);
53+
DbxWrappedException.executeOtherBlocks(factory, routeError);
54+
55+
return new DbxWrappedException(routeError, requestId, apiResponse.getUserMessage());
56+
}
57+
58+
public static void executeOtherBlocks(DbxGlobalCallbackFactory factory, Object routeError) {
59+
try {
60+
// Recursively looks at union errors and the union's current tag type. If there is a handler
61+
// for the current tag type, it is executed.
62+
Method m = routeError.getClass().getMethod("tag");
63+
Object result = m.invoke(routeError);
64+
String fName = result.toString().toLowerCase() + "value";
65+
for(Field f : routeError.getClass().getDeclaredFields() ) {
66+
if (f.getName().equalsIgnoreCase(fName) ) {
67+
f.setAccessible(true);
68+
Object fieldValue = f.get(routeError);
69+
DbxWrappedException.executeBlockForObject(factory, fieldValue);
70+
break;
71+
}
72+
}
73+
} catch (Exception e) {
74+
// No handling
75+
}
76+
}
77+
78+
public static <T> void executeBlockForObject(DbxGlobalCallbackFactory factory, T routeError) {
79+
if (factory != null) {
80+
DbxRouteErrorCallback<T> callback = factory.createRouteErrorCallback(routeError);
81+
if (callback != null) {
82+
callback.setRouteError(routeError);
83+
callback.run();
84+
}
85+
}
4686
}
4787
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.dropbox.core.v2.callbacks;
2+
3+
/**
4+
* Client should implement methods in this class to allow for consistent, global handling of route-specific error types,
5+
* as well as general network errors. Normally, error handling is done on a request-by-request basis.
6+
* However, it is convenient to handle some error behavior consistently, regardless of the request or the endpoint.
7+
* For implementing global error handling for general networking errors like an auth error, `createNetworkErrorCallback`
8+
* should be implemented. For implementing global error handling for route-specific errors, the
9+
* `createRouteErrorCallback` should be implemented.
10+
*/
11+
public interface DbxGlobalCallbackFactory {
12+
// Should instantiate separate callback object on each method invocation
13+
<T> DbxRouteErrorCallback<T> createRouteErrorCallback(T routeError);
14+
15+
// Should instantiate separate callback object on each method invocation
16+
DbxNetworkErrorCallback createNetworkErrorCallback();
17+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.dropbox.core.v2.callbacks;
2+
3+
import com.dropbox.core.DbxException;
4+
5+
/**
6+
* Abstract class for network error callback.
7+
*/
8+
public abstract class DbxNetworkErrorCallback implements Runnable {
9+
private DbxException networkError = null;
10+
11+
public DbxException getNetworkError() {
12+
return networkError;
13+
}
14+
15+
public void setNetworkError(DbxException networkError) {
16+
this.networkError = networkError;
17+
}
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.dropbox.core.v2.callbacks;
2+
3+
/**
4+
* Interface for route error callback.
5+
*/
6+
public abstract class DbxRouteErrorCallback<T> implements Runnable {
7+
private T routeError = null;
8+
9+
public T getRouteError() {
10+
return routeError;
11+
}
12+
13+
public void setRouteError(T routeError) {
14+
this.routeError = routeError;
15+
}
16+
}

0 commit comments

Comments
 (0)