Skip to content

Commit 76606b5

Browse files
committed
Merge pull request #109 from gcurtis/connection-restore
Improve saving and restoring BoxAPIConnections
2 parents 0a63b46 + 6b73ca0 commit 76606b5

File tree

3 files changed

+168
-3
lines changed

3 files changed

+168
-3
lines changed

src/main/java/com/box/sdk/BoxAPIConnection.java

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,42 @@ public BoxAPIConnection(String clientID, String clientSecret) {
102102
this(clientID, clientSecret, null, null);
103103
}
104104

105+
/**
106+
* Restores a BoxAPIConnection from a saved state.
107+
*
108+
* @see #save
109+
* @param state the saved state that was created with {@link #save}.
110+
* @return a restored API connection.
111+
*/
112+
public static BoxAPIConnection restore(String state) {
113+
JsonObject json = JsonObject.readFrom(state);
114+
String clientID = json.get("clientID").asString();
115+
String clientSecret = json.get("clientSecret").asString();
116+
String accessToken = json.get("accessToken").asString();
117+
String refreshToken = json.get("refreshToken").asString();
118+
long lastRefresh = json.get("lastRefresh").asLong();
119+
long expires = json.get("expires").asLong();
120+
String userAgent = json.get("userAgent").asString();
121+
String tokenURL = json.get("tokenURL").asString();
122+
String baseURL = json.get("baseURL").asString();
123+
String baseUploadURL = json.get("baseUploadURL").asString();
124+
boolean autoRefresh = json.get("autoRefresh").asBoolean();
125+
int maxRequestAttempts = json.get("maxRequestAttempts").asInt();
126+
127+
BoxAPIConnection api = new BoxAPIConnection(clientID, clientSecret, accessToken, refreshToken);
128+
api.accessToken = accessToken;
129+
api.refreshToken = refreshToken;
130+
api.lastRefresh = lastRefresh;
131+
api.expires = expires;
132+
api.userAgent = userAgent;
133+
api.tokenURL = tokenURL;
134+
api.baseURL = baseURL;
135+
api.baseUploadURL = baseUploadURL;
136+
api.autoRefresh = autoRefresh;
137+
api.maxRequestAttempts = maxRequestAttempts;
138+
return api;
139+
}
140+
105141
/**
106142
* Authenticates the API connection by obtaining access and refresh tokens using the auth code that was obtained
107143
* from the first half of OAuth.
@@ -262,6 +298,27 @@ public void setRefreshToken(String refreshToken) {
262298
this.refreshToken = refreshToken;
263299
}
264300

301+
/**
302+
* Gets the last time that the access token was refreshed.
303+
*
304+
* @return the last refresh time in milliseconds.
305+
*/
306+
public long getLastRefresh() {
307+
return this.lastRefresh;
308+
}
309+
310+
/**
311+
* Sets the last time that the access token was refreshed.
312+
*
313+
* <p>This value is used when determining if an access token needs to be auto-refreshed. If the amount of time since
314+
* the last refresh exceeds the access token's expiration time, then the access token will be refreshed.</p>
315+
*
316+
* @param lastRefresh the new last refresh time in milliseconds.
317+
*/
318+
public void setLastRefresh(long lastRefresh) {
319+
this.lastRefresh = lastRefresh;
320+
}
321+
265322
/**
266323
* Enables or disables automatic refreshing of this connection's access token. Defaults to true.
267324
* @param autoRefresh true to enable auto token refresh; otherwise false.
@@ -354,6 +411,7 @@ public void refresh() {
354411
BoxJSONResponse response = (BoxJSONResponse) request.send();
355412
json = response.getJSON();
356413
} catch (BoxAPIException e) {
414+
this.notifyError(e);
357415
this.refreshLock.writeLock().unlock();
358416
throw e;
359417
}
@@ -370,11 +428,20 @@ public void refresh() {
370428
}
371429

372430
/**
373-
* Notifies refresh event to all the listeners.
431+
* Notifies a refresh event to all the listeners.
374432
*/
375433
private void notifyRefresh() {
376434
for (BoxAPIConnectionListener listener : this.listeners) {
377-
listener.onRefresh();
435+
listener.onRefresh(this);
436+
}
437+
}
438+
439+
/**
440+
* Notifies an error event to all the listeners.
441+
*/
442+
private void notifyError(BoxAPIException error) {
443+
for (BoxAPIConnectionListener listener : this.listeners) {
444+
listener.onError(this, error);
378445
}
379446
}
380447

@@ -409,4 +476,27 @@ public RequestInterceptor getRequestInterceptor() {
409476
public void setRequestInterceptor(RequestInterceptor interceptor) {
410477
this.interceptor = interceptor;
411478
}
479+
480+
/**
481+
* Saves the state of this connection to a string so that it can be persisted and restored at a later time.
482+
*
483+
* @see #restore
484+
* @return the state of this connection.
485+
*/
486+
public String save() {
487+
JsonObject state = new JsonObject()
488+
.add("clientID", this.clientID)
489+
.add("clientSecret", this.clientSecret)
490+
.add("accessToken", this.accessToken)
491+
.add("refreshToken", this.refreshToken)
492+
.add("lastRefresh", this.lastRefresh)
493+
.add("expires", this.expires)
494+
.add("userAgent", this.userAgent)
495+
.add("tokenURL", this.tokenURL)
496+
.add("baseURL", this.baseURL)
497+
.add("baseUploadURL", this.baseUploadURL)
498+
.add("autoRefresh", this.autoRefresh)
499+
.add("maxRequestAttempts", this.maxRequestAttempts);
500+
return state.toString();
501+
}
412502
}

src/main/java/com/box/sdk/BoxAPIConnectionListener.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ public interface BoxAPIConnectionListener {
77

88
/**
99
* Called when the Box API connection refreshes its tokens.
10+
*
11+
* <p>The provided connection is guaranteed to not be auto-refreshed or modified by another listener until this
12+
* method returns.</p>
13+
*
14+
* @param api the API connection that was refreshed.
1015
*/
11-
void onRefresh();
16+
void onRefresh(BoxAPIConnection api);
17+
18+
/**
19+
* Called when an error occurs while attempting to refresh the Box API connection's tokens.
20+
*
21+
* <p>The provided connection is guaranteed to not be auto-refreshed or modified by another listener until this
22+
* method returns.</p>
23+
*
24+
* @param api the API connection that encountered an error.
25+
* @param error the error that occurred.
26+
*/
27+
void onError(BoxAPIConnection api, BoxAPIException error);
1228
}

src/test/java/com/box/sdk/BoxAPIConnectionTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66
import static org.hamcrest.Matchers.equalTo;
77
import static org.hamcrest.Matchers.is;
88
import static org.hamcrest.Matchers.not;
9+
import static org.junit.Assert.assertFalse;
910
import static org.junit.Assert.assertThat;
11+
import static org.junit.Assert.fail;
1012
import static org.mockito.Matchers.any;
1113
import static org.mockito.Mockito.mock;
1214
import static org.mockito.Mockito.when;
1315

1416
import org.junit.Test;
1517
import org.junit.experimental.categories.Category;
1618

19+
import com.eclipsesource.json.JsonObject;
20+
1721
public class BoxAPIConnectionTest {
1822
@Test
1923
@Category(UnitTest.class)
@@ -78,6 +82,45 @@ public void interceptorReceivesSentRequest() throws MalformedURLException {
7882
assertThat(response, is(equalTo(fakeResponse)));
7983
}
8084

85+
@Test
86+
@Category(UnitTest.class)
87+
public void restoreConnectionThatDoesNotNeedRefresh() {
88+
BoxAPIConnection api = new BoxAPIConnection("fake client ID", "fake client secret", "fake access token",
89+
"fake refresh token");
90+
api.setExpires(3600000L);
91+
api.setLastRefresh(System.currentTimeMillis());
92+
String state = api.save();
93+
94+
final BoxAPIConnection restoredAPI = BoxAPIConnection.restore(state);
95+
restoredAPI.setRequestInterceptor(new RequestInterceptor() {
96+
@Override
97+
public BoxAPIResponse onRequest(BoxAPIRequest request) {
98+
String tokenURLString = restoredAPI.getTokenURL().toString();
99+
String requestURLString = request.getUrl().toString();
100+
if (requestURLString.contains(tokenURLString)) {
101+
fail("The connection was refreshed.");
102+
}
103+
104+
if (requestURLString.contains("folders")) {
105+
return new BoxJSONResponse() {
106+
@Override
107+
public String getJSON() {
108+
JsonObject responseJSON = new JsonObject()
109+
.add("id", "fake ID")
110+
.add("type", "folder");
111+
return responseJSON.toString();
112+
}
113+
};
114+
}
115+
116+
fail("Unexpected request.");
117+
return null;
118+
}
119+
});
120+
121+
assertFalse(restoredAPI.needsRefresh());
122+
}
123+
81124
@Test
82125
@Category(IntegrationTest.class)
83126
public void requestIsSentNormallyWhenInterceptorReturnsNullResponse() throws MalformedURLException {
@@ -150,4 +193,20 @@ public void doesNotRefreshWhenGetAccessTokenIsCalledAndTokenHasNotExpired() {
150193
TestConfig.setAccessToken(actualAccessToken);
151194
TestConfig.setRefreshToken(actualRefreshToken);
152195
}
196+
197+
@Test
198+
@Category(IntegrationTest.class)
199+
public void successfullySavesAndRestoresConnection() {
200+
final String originalAccessToken = TestConfig.getAccessToken();
201+
final String originalRefreshToken = TestConfig.getRefreshToken();
202+
BoxAPIConnection api = new BoxAPIConnection(TestConfig.getClientID(), TestConfig.getClientSecret(),
203+
originalAccessToken, originalRefreshToken);
204+
String state = api.save();
205+
206+
BoxAPIConnection restoredAPI = BoxAPIConnection.restore(state);
207+
BoxFolder.Info rootFolderInfo = BoxFolder.getRootFolder(restoredAPI).getInfo();
208+
209+
TestConfig.setAccessToken(restoredAPI.getAccessToken());
210+
TestConfig.setRefreshToken(restoredAPI.getRefreshToken());
211+
}
153212
}

0 commit comments

Comments
 (0)