2012-10-26 17:37:49 -07:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
package org.mozilla.gecko.background.announcements;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.security.GeneralSecurityException;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import org.json.simple.JSONArray;
|
|
|
|
import org.json.simple.JSONObject;
|
2013-02-27 15:44:21 -08:00
|
|
|
import org.mozilla.gecko.background.common.log.Logger;
|
2012-10-26 17:37:49 -07:00
|
|
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
|
|
|
import org.mozilla.gecko.sync.NonArrayJSONException;
|
2012-11-16 10:08:55 -08:00
|
|
|
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
2012-10-26 17:37:49 -07:00
|
|
|
import org.mozilla.gecko.sync.net.BaseResource;
|
2012-11-16 10:08:55 -08:00
|
|
|
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
|
2012-10-26 17:37:49 -07:00
|
|
|
import org.mozilla.gecko.sync.net.Resource;
|
|
|
|
import org.mozilla.gecko.sync.net.SyncResponse;
|
|
|
|
|
2012-12-04 10:58:07 -08:00
|
|
|
import ch.boye.httpclientandroidlib.Header;
|
2012-10-26 17:37:49 -07:00
|
|
|
import ch.boye.httpclientandroidlib.HttpResponse;
|
|
|
|
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
|
|
|
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
|
|
|
|
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
|
|
|
import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
|
2012-12-04 10:58:07 -08:00
|
|
|
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
2012-10-26 17:37:49 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts HTTP resource callbacks into AnnouncementsFetchDelegate callbacks.
|
|
|
|
*/
|
2012-11-16 10:08:55 -08:00
|
|
|
public class AnnouncementsFetchResourceDelegate extends BaseResourceDelegate {
|
2012-10-26 17:37:49 -07:00
|
|
|
private static final String ACCEPT_HEADER = "application/json;charset=utf-8";
|
|
|
|
|
|
|
|
private static final String LOG_TAG = "AnnounceFetchRD";
|
|
|
|
|
|
|
|
protected final long startTime;
|
|
|
|
protected AnnouncementsFetchDelegate delegate;
|
|
|
|
|
|
|
|
public AnnouncementsFetchResourceDelegate(Resource resource, AnnouncementsFetchDelegate delegate) {
|
|
|
|
super(resource);
|
|
|
|
this.startTime = System.currentTimeMillis();
|
|
|
|
this.delegate = delegate;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
|
|
|
|
super.addHeaders(request, client);
|
|
|
|
|
|
|
|
// The basics.
|
|
|
|
request.addHeader("User-Agent", delegate.getUserAgent());
|
|
|
|
request.addHeader("Accept-Language", delegate.getLocale().toString());
|
|
|
|
request.addHeader("Accept", ACCEPT_HEADER);
|
|
|
|
|
2012-11-01 12:29:10 -07:00
|
|
|
// We never want to keep connections alive.
|
|
|
|
request.addHeader("Connection", "close");
|
|
|
|
|
2012-10-26 17:37:49 -07:00
|
|
|
// Set If-Modified-Since to avoid re-fetching content.
|
2012-12-04 10:58:07 -08:00
|
|
|
final String ifModifiedSince = delegate.getLastDate();
|
|
|
|
if (ifModifiedSince != null) {
|
|
|
|
Logger.info(LOG_TAG, "If-Modified-Since: " + ifModifiedSince);
|
|
|
|
request.addHeader("If-Modified-Since", ifModifiedSince);
|
2012-10-26 17:37:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Just in case.
|
|
|
|
request.removeHeaders("Cookie");
|
|
|
|
}
|
|
|
|
|
|
|
|
private List<Announcement> parseBody(ExtendedJSONObject body) throws NonArrayJSONException {
|
|
|
|
List<Announcement> out = new ArrayList<Announcement>(1);
|
|
|
|
JSONArray snippets = body.getArray("announcements");
|
|
|
|
if (snippets == null) {
|
|
|
|
Logger.warn(LOG_TAG, "Missing announcements body. Returning empty.");
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Object s : snippets) {
|
|
|
|
try {
|
|
|
|
out.add(Announcement.parseAnnouncement(new ExtendedJSONObject((JSONObject) s)));
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.warn(LOG_TAG, "Malformed announcement or display failed. Skipping.", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void handleHttpResponse(HttpResponse response) {
|
2012-12-04 10:58:07 -08:00
|
|
|
final Header dateHeader = response.getFirstHeader(HTTP.DATE_HEADER);
|
|
|
|
String date = null;
|
|
|
|
if (dateHeader != null) {
|
|
|
|
// Note that we are deliberately not validating the server time here.
|
|
|
|
// We pass it directly back to the server; we don't care about the
|
|
|
|
// contents, and if we reject a value we essentially re-initialize
|
|
|
|
// the client, which will cause stale announcements to be re-fetched.
|
|
|
|
date = dateHeader.getValue();
|
|
|
|
}
|
|
|
|
if (date == null) {
|
|
|
|
// Use local clock, because skipping is better than re-fetching.
|
|
|
|
date = DateUtils.formatDate(new Date());
|
|
|
|
Logger.warn(LOG_TAG, "No fetch date; using local time " + date);
|
|
|
|
}
|
2012-10-26 17:37:49 -07:00
|
|
|
|
2012-12-04 10:58:07 -08:00
|
|
|
final SyncResponse r = new SyncResponse(response); // For convenience.
|
2012-10-26 17:37:49 -07:00
|
|
|
try {
|
|
|
|
final int statusCode = r.getStatusCode();
|
|
|
|
Logger.debug(LOG_TAG, "Got announcements response: " + statusCode);
|
|
|
|
|
|
|
|
if (statusCode == 204 || statusCode == 304) {
|
|
|
|
BaseResource.consumeEntity(response);
|
2012-12-04 10:58:07 -08:00
|
|
|
delegate.onNoNewAnnouncements(startTime, date);
|
2012-10-26 17:37:49 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (statusCode == 200) {
|
|
|
|
final List<Announcement> snippets;
|
|
|
|
try {
|
|
|
|
snippets = parseBody(r.jsonObjectBody());
|
|
|
|
} catch (Exception e) {
|
|
|
|
delegate.onRemoteError(e);
|
|
|
|
return;
|
|
|
|
}
|
2012-12-04 10:58:07 -08:00
|
|
|
delegate.onNewAnnouncements(snippets, startTime, date);
|
2012-10-26 17:37:49 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (statusCode == 400 || statusCode == 405) {
|
|
|
|
// We did something wrong.
|
|
|
|
Logger.warn(LOG_TAG, "We did something wrong. Oh dear.");
|
|
|
|
// Fall through.
|
|
|
|
}
|
|
|
|
|
|
|
|
if (statusCode == 503 || statusCode == 500) {
|
|
|
|
Logger.warn(LOG_TAG, "Server issue: " + r.body());
|
|
|
|
delegate.onBackoff(r.retryAfterInSeconds());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, clean up.
|
|
|
|
delegate.onRemoteFailure(statusCode);
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.warn(LOG_TAG, "Failed to extract body.", e);
|
|
|
|
delegate.onRemoteError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void handleHttpProtocolException(ClientProtocolException e) {
|
|
|
|
Logger.warn(LOG_TAG, "Protocol exception.", e);
|
|
|
|
delegate.onLocalError(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void handleHttpIOException(IOException e) {
|
|
|
|
Logger.warn(LOG_TAG, "IO exception.", e);
|
|
|
|
delegate.onLocalError(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void handleTransportException(GeneralSecurityException e) {
|
|
|
|
Logger.warn(LOG_TAG, "Transport exception.", e);
|
|
|
|
// Class this as a remote error, because it's probably something odd
|
|
|
|
// with SSL negotiation.
|
|
|
|
delegate.onRemoteError(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Be very thorough in case the superclass implementation changes.
|
|
|
|
* We never want this to be an authenticated request.
|
|
|
|
*/
|
|
|
|
@Override
|
2012-11-16 10:08:55 -08:00
|
|
|
public AuthHeaderProvider getAuthHeaderProvider() {
|
2012-10-26 17:37:49 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|