mobile/android/base/background/announcements/AnnouncementsFetchResourceDelegate.java

branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
equal deleted inserted replaced
-1:000000000000 0:51fc567b123b
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 package org.mozilla.gecko.background.announcements;
6
7 import java.io.IOException;
8 import java.security.GeneralSecurityException;
9 import java.util.ArrayList;
10 import java.util.Date;
11 import java.util.List;
12
13 import org.json.simple.JSONArray;
14 import org.json.simple.JSONObject;
15 import org.mozilla.gecko.background.common.log.Logger;
16 import org.mozilla.gecko.sync.ExtendedJSONObject;
17 import org.mozilla.gecko.sync.NonArrayJSONException;
18 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
19 import org.mozilla.gecko.sync.net.BaseResource;
20 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
21 import org.mozilla.gecko.sync.net.Resource;
22 import org.mozilla.gecko.sync.net.SyncResponse;
23
24 import ch.boye.httpclientandroidlib.Header;
25 import ch.boye.httpclientandroidlib.HttpResponse;
26 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
27 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
28 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
29 import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
30 import ch.boye.httpclientandroidlib.protocol.HTTP;
31
32 /**
33 * Converts HTTP resource callbacks into AnnouncementsFetchDelegate callbacks.
34 */
35 public class AnnouncementsFetchResourceDelegate extends BaseResourceDelegate {
36 private static final String ACCEPT_HEADER = "application/json;charset=utf-8";
37
38 private static final String LOG_TAG = "AnnounceFetchRD";
39
40 protected final long startTime;
41 protected AnnouncementsFetchDelegate delegate;
42
43 public AnnouncementsFetchResourceDelegate(Resource resource, AnnouncementsFetchDelegate delegate) {
44 super(resource);
45 this.startTime = System.currentTimeMillis();
46 this.delegate = delegate;
47 }
48
49 @Override
50 public String getUserAgent() {
51 return delegate.getUserAgent();
52 }
53
54 @Override
55 public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
56 super.addHeaders(request, client);
57
58 // The basics.
59 request.addHeader("Accept-Language", delegate.getLocale().toString());
60 request.addHeader("Accept", ACCEPT_HEADER);
61
62 // We never want to keep connections alive.
63 request.addHeader("Connection", "close");
64
65 // Set If-Modified-Since to avoid re-fetching content.
66 final String ifModifiedSince = delegate.getLastDate();
67 if (ifModifiedSince != null) {
68 Logger.info(LOG_TAG, "If-Modified-Since: " + ifModifiedSince);
69 request.addHeader("If-Modified-Since", ifModifiedSince);
70 }
71
72 // Just in case.
73 request.removeHeaders("Cookie");
74 }
75
76 private List<Announcement> parseBody(ExtendedJSONObject body) throws NonArrayJSONException {
77 List<Announcement> out = new ArrayList<Announcement>(1);
78 JSONArray snippets = body.getArray("announcements");
79 if (snippets == null) {
80 Logger.warn(LOG_TAG, "Missing announcements body. Returning empty.");
81 return out;
82 }
83
84 for (Object s : snippets) {
85 try {
86 out.add(Announcement.parseAnnouncement(new ExtendedJSONObject((JSONObject) s)));
87 } catch (Exception e) {
88 Logger.warn(LOG_TAG, "Malformed announcement or display failed. Skipping.", e);
89 }
90 }
91 return out;
92 }
93
94 @Override
95 public void handleHttpResponse(HttpResponse response) {
96 final Header dateHeader = response.getFirstHeader(HTTP.DATE_HEADER);
97 String date = null;
98 if (dateHeader != null) {
99 // Note that we are deliberately not validating the server time here.
100 // We pass it directly back to the server; we don't care about the
101 // contents, and if we reject a value we essentially re-initialize
102 // the client, which will cause stale announcements to be re-fetched.
103 date = dateHeader.getValue();
104 }
105 if (date == null) {
106 // Use local clock, because skipping is better than re-fetching.
107 date = DateUtils.formatDate(new Date());
108 Logger.warn(LOG_TAG, "No fetch date; using local time " + date);
109 }
110
111 final SyncResponse r = new SyncResponse(response); // For convenience.
112 try {
113 final int statusCode = r.getStatusCode();
114 Logger.debug(LOG_TAG, "Got announcements response: " + statusCode);
115
116 if (statusCode == 204 || statusCode == 304) {
117 BaseResource.consumeEntity(response);
118 delegate.onNoNewAnnouncements(startTime, date);
119 return;
120 }
121
122 if (statusCode == 200) {
123 final List<Announcement> snippets;
124 try {
125 snippets = parseBody(r.jsonObjectBody());
126 } catch (Exception e) {
127 delegate.onRemoteError(e);
128 return;
129 }
130 delegate.onNewAnnouncements(snippets, startTime, date);
131 return;
132 }
133
134 if (statusCode == 400 || statusCode == 405) {
135 // We did something wrong.
136 Logger.warn(LOG_TAG, "We did something wrong. Oh dear.");
137 // Fall through.
138 }
139
140 if (statusCode == 503 || statusCode == 500) {
141 Logger.warn(LOG_TAG, "Server issue: " + r.body());
142 delegate.onBackoff(r.retryAfterInSeconds());
143 return;
144 }
145
146 // Otherwise, clean up.
147 delegate.onRemoteFailure(statusCode);
148
149 } catch (Exception e) {
150 Logger.warn(LOG_TAG, "Failed to extract body.", e);
151 delegate.onRemoteError(e);
152 }
153 }
154
155 @Override
156 public void handleHttpProtocolException(ClientProtocolException e) {
157 Logger.warn(LOG_TAG, "Protocol exception.", e);
158 delegate.onLocalError(e);
159 }
160
161 @Override
162 public void handleHttpIOException(IOException e) {
163 Logger.warn(LOG_TAG, "IO exception.", e);
164 delegate.onLocalError(e);
165 }
166
167 @Override
168 public void handleTransportException(GeneralSecurityException e) {
169 Logger.warn(LOG_TAG, "Transport exception.", e);
170 // Class this as a remote error, because it's probably something odd
171 // with SSL negotiation.
172 delegate.onRemoteError(e);
173 }
174
175 /**
176 * Be very thorough in case the superclass implementation changes.
177 * We never want this to be an authenticated request.
178 */
179 @Override
180 public AuthHeaderProvider getAuthHeaderProvider() {
181 return null;
182 }
183 }

mercurial