michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.sync; michael@0: michael@0: import java.util.concurrent.CountDownLatch; michael@0: import java.util.concurrent.TimeUnit; michael@0: michael@0: import org.mozilla.gecko.background.common.log.Logger; michael@0: import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate; michael@0: import org.mozilla.gecko.sync.net.AuthHeaderProvider; michael@0: import org.mozilla.gecko.sync.net.SyncStorageRecordRequest; michael@0: import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate; michael@0: import org.mozilla.gecko.sync.net.SyncStorageResponse; michael@0: michael@0: /** michael@0: * An object which fetches a chunk of JSON from a URI, using certain credentials, michael@0: * and informs its delegate of the result. michael@0: */ michael@0: public class JSONRecordFetcher { michael@0: private static final long DEFAULT_AWAIT_TIMEOUT_MSEC = 2 * 60 * 1000; // Two minutes. michael@0: private static final String LOG_TAG = "JSONRecordFetcher"; michael@0: michael@0: protected final AuthHeaderProvider authHeaderProvider; michael@0: protected final String uri; michael@0: protected JSONRecordFetchDelegate delegate; michael@0: michael@0: public JSONRecordFetcher(final String uri, final AuthHeaderProvider authHeaderProvider) { michael@0: if (uri == null) { michael@0: throw new IllegalArgumentException("uri must not be null"); michael@0: } michael@0: this.uri = uri; michael@0: this.authHeaderProvider = authHeaderProvider; michael@0: } michael@0: michael@0: protected String getURI() { michael@0: return this.uri; michael@0: } michael@0: michael@0: private class JSONFetchHandler implements SyncStorageRequestDelegate { michael@0: michael@0: // SyncStorageRequestDelegate methods for fetching. michael@0: @Override michael@0: public AuthHeaderProvider getAuthHeaderProvider() { michael@0: return authHeaderProvider; michael@0: } michael@0: michael@0: public String ifUnmodifiedSince() { michael@0: return null; michael@0: } michael@0: michael@0: public void handleRequestSuccess(SyncStorageResponse response) { michael@0: if (response.wasSuccessful()) { michael@0: try { michael@0: delegate.handleSuccess(response.jsonObjectBody()); michael@0: } catch (Exception e) { michael@0: handleRequestError(e); michael@0: } michael@0: return; michael@0: } michael@0: handleRequestFailure(response); michael@0: } michael@0: michael@0: @Override michael@0: public void handleRequestFailure(SyncStorageResponse response) { michael@0: delegate.handleFailure(response); michael@0: } michael@0: michael@0: @Override michael@0: public void handleRequestError(Exception ex) { michael@0: delegate.handleError(ex); michael@0: } michael@0: } michael@0: michael@0: public void fetch(final JSONRecordFetchDelegate delegate) { michael@0: this.delegate = delegate; michael@0: try { michael@0: final SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.getURI()); michael@0: r.delegate = new JSONFetchHandler(); michael@0: r.get(); michael@0: } catch (Exception e) { michael@0: delegate.handleError(e); michael@0: } michael@0: } michael@0: michael@0: private class LatchedJSONRecordFetchDelegate implements JSONRecordFetchDelegate { michael@0: public ExtendedJSONObject body = null; michael@0: public Exception exception = null; michael@0: private CountDownLatch latch; michael@0: michael@0: public LatchedJSONRecordFetchDelegate(CountDownLatch latch) { michael@0: this.latch = latch; michael@0: } michael@0: michael@0: @Override michael@0: public void handleFailure(SyncStorageResponse response) { michael@0: this.exception = new HTTPFailureException(response); michael@0: latch.countDown(); michael@0: } michael@0: michael@0: @Override michael@0: public void handleError(Exception e) { michael@0: this.exception = e; michael@0: latch.countDown(); michael@0: } michael@0: michael@0: @Override michael@0: public void handleSuccess(ExtendedJSONObject body) { michael@0: this.body = body; michael@0: latch.countDown(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Fetch the info record, blocking until it returns. michael@0: * @return the info record. michael@0: */ michael@0: public ExtendedJSONObject fetchBlocking() throws HTTPFailureException, Exception { michael@0: CountDownLatch latch = new CountDownLatch(1); michael@0: LatchedJSONRecordFetchDelegate delegate = new LatchedJSONRecordFetchDelegate(latch); michael@0: this.delegate = delegate; michael@0: this.fetch(delegate); michael@0: michael@0: // Sanity wait: the resource itself will time out and throw after two michael@0: // minutes, so we just want to avoid coding errors causing us to block michael@0: // endlessly. michael@0: if (!latch.await(DEFAULT_AWAIT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)) { michael@0: Logger.warn(LOG_TAG, "Interrupted fetching info record."); michael@0: throw new InterruptedException("info fetch timed out."); michael@0: } michael@0: michael@0: if (delegate.body != null) { michael@0: return delegate.body; michael@0: } michael@0: michael@0: if (delegate.exception != null) { michael@0: throw delegate.exception; michael@0: } michael@0: michael@0: throw new Exception("Unknown error."); michael@0: } michael@0: }