mobile/android/base/background/bagheera/BagheeraClient.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.background.bagheera;
michael@0 6
michael@0 7 import java.io.IOException;
michael@0 8 import java.net.URISyntaxException;
michael@0 9 import java.security.GeneralSecurityException;
michael@0 10 import java.util.Collection;
michael@0 11 import java.util.concurrent.Executor;
michael@0 12 import java.util.concurrent.Executors;
michael@0 13 import java.util.regex.Pattern;
michael@0 14
michael@0 15 import org.mozilla.gecko.sync.Utils;
michael@0 16 import org.mozilla.gecko.sync.net.BaseResource;
michael@0 17 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
michael@0 18 import org.mozilla.gecko.sync.net.Resource;
michael@0 19
michael@0 20 import ch.boye.httpclientandroidlib.HttpEntity;
michael@0 21 import ch.boye.httpclientandroidlib.HttpResponse;
michael@0 22 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
michael@0 23 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
michael@0 24 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
michael@0 25 import ch.boye.httpclientandroidlib.protocol.HTTP;
michael@0 26
michael@0 27 /**
michael@0 28 * Provides encapsulated access to a Bagheera document server.
michael@0 29 * The two permitted operations are:
michael@0 30 * * Delete a document.
michael@0 31 * * Upload a document, optionally deleting an expired document.
michael@0 32 */
michael@0 33 public class BagheeraClient {
michael@0 34
michael@0 35 protected final String serverURI;
michael@0 36 protected final Executor executor;
michael@0 37 protected static final Pattern URI_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
michael@0 38
michael@0 39 protected static String PROTOCOL_VERSION = "1.0";
michael@0 40 protected static String SUBMIT_PATH = "/submit/";
michael@0 41
michael@0 42 /**
michael@0 43 * Instantiate a new client pointing at the provided server.
michael@0 44 * {@link #deleteDocument(String, String, BagheeraRequestDelegate)} and
michael@0 45 * {@link #uploadJSONDocument(String, String, String, String, BagheeraRequestDelegate)}
michael@0 46 * both accept delegate arguments; the {@link Executor} provided to this
michael@0 47 * constructor will be used to invoke callbacks on those delegates.
michael@0 48 *
michael@0 49 * @param serverURI
michael@0 50 * the destination server URI.
michael@0 51 * @param executor
michael@0 52 * the executor which will be used to invoke delegate callbacks.
michael@0 53 */
michael@0 54 public BagheeraClient(final String serverURI, final Executor executor) {
michael@0 55 if (serverURI == null) {
michael@0 56 throw new IllegalArgumentException("Must provide a server URI.");
michael@0 57 }
michael@0 58 if (executor == null) {
michael@0 59 throw new IllegalArgumentException("Must provide a non-null executor.");
michael@0 60 }
michael@0 61 this.serverURI = serverURI.endsWith("/") ? serverURI : serverURI + "/";
michael@0 62 this.executor = executor;
michael@0 63 }
michael@0 64
michael@0 65 /**
michael@0 66 * Instantiate a new client pointing at the provided server.
michael@0 67 * Delegate callbacks will be invoked on a new background thread.
michael@0 68 *
michael@0 69 * See {@link #BagheeraClient(String, Executor)} for more details.
michael@0 70 *
michael@0 71 * @param serverURI
michael@0 72 * the destination server URI.
michael@0 73 */
michael@0 74 public BagheeraClient(final String serverURI) {
michael@0 75 this(serverURI, Executors.newSingleThreadExecutor());
michael@0 76 }
michael@0 77
michael@0 78 /**
michael@0 79 * Delete the specified document from the server.
michael@0 80 * The delegate's callbacks will be invoked by the BagheeraClient's executor.
michael@0 81 */
michael@0 82 public void deleteDocument(final String namespace,
michael@0 83 final String id,
michael@0 84 final BagheeraRequestDelegate delegate) throws URISyntaxException {
michael@0 85 if (namespace == null) {
michael@0 86 throw new IllegalArgumentException("Must provide namespace.");
michael@0 87 }
michael@0 88 if (id == null) {
michael@0 89 throw new IllegalArgumentException("Must provide id.");
michael@0 90 }
michael@0 91
michael@0 92 final BaseResource resource = makeResource(namespace, id);
michael@0 93 resource.delegate = new BagheeraResourceDelegate(resource, namespace, id, delegate);
michael@0 94 resource.delete();
michael@0 95 }
michael@0 96
michael@0 97 /**
michael@0 98 * Upload a JSON document to a Bagheera server. The delegate's callbacks will
michael@0 99 * be invoked in tasks run by the client's executor.
michael@0 100 *
michael@0 101 * @param namespace
michael@0 102 * the namespace, such as "test"
michael@0 103 * @param id
michael@0 104 * the document ID, which is typically a UUID.
michael@0 105 * @param payload
michael@0 106 * a document, typically JSON-encoded.
michael@0 107 * @param oldIDs
michael@0 108 * an optional collection of IDs which denote documents to supersede. Can be null or empty.
michael@0 109 * @param delegate
michael@0 110 * the delegate whose methods should be invoked on success or
michael@0 111 * failure.
michael@0 112 */
michael@0 113 public void uploadJSONDocument(final String namespace,
michael@0 114 final String id,
michael@0 115 final String payload,
michael@0 116 Collection<String> oldIDs,
michael@0 117 final BagheeraRequestDelegate delegate) throws URISyntaxException {
michael@0 118 if (namespace == null) {
michael@0 119 throw new IllegalArgumentException("Must provide namespace.");
michael@0 120 }
michael@0 121 if (id == null) {
michael@0 122 throw new IllegalArgumentException("Must provide id.");
michael@0 123 }
michael@0 124 if (payload == null) {
michael@0 125 throw new IllegalArgumentException("Must provide payload.");
michael@0 126 }
michael@0 127
michael@0 128 final BaseResource resource = makeResource(namespace, id);
michael@0 129 final HttpEntity deflatedBody = DeflateHelper.deflateBody(payload);
michael@0 130
michael@0 131 resource.delegate = new BagheeraUploadResourceDelegate(resource, namespace, id, oldIDs, delegate);
michael@0 132 resource.post(deflatedBody);
michael@0 133 }
michael@0 134
michael@0 135 public static boolean isValidURIComponent(final String in) {
michael@0 136 return URI_PATTERN.matcher(in).matches();
michael@0 137 }
michael@0 138
michael@0 139 protected BaseResource makeResource(final String namespace, final String id) throws URISyntaxException {
michael@0 140 if (!isValidURIComponent(namespace)) {
michael@0 141 throw new URISyntaxException(namespace, "Illegal namespace name. Must be alphanumeric + [_-].");
michael@0 142 }
michael@0 143
michael@0 144 if (!isValidURIComponent(id)) {
michael@0 145 throw new URISyntaxException(id, "Illegal id value. Must be alphanumeric + [_-].");
michael@0 146 }
michael@0 147
michael@0 148 final String uri = this.serverURI + PROTOCOL_VERSION + SUBMIT_PATH +
michael@0 149 namespace + "/" + id;
michael@0 150 return new BaseResource(uri);
michael@0 151 }
michael@0 152
michael@0 153 public class BagheeraResourceDelegate extends BaseResourceDelegate {
michael@0 154 private static final int DEFAULT_SOCKET_TIMEOUT_MSEC = 5 * 60 * 1000; // Five minutes.
michael@0 155 protected final BagheeraRequestDelegate delegate;
michael@0 156 protected final String namespace;
michael@0 157 protected final String id;
michael@0 158
michael@0 159 public BagheeraResourceDelegate(final Resource resource,
michael@0 160 final String namespace,
michael@0 161 final String id,
michael@0 162 final BagheeraRequestDelegate delegate) {
michael@0 163 super(resource);
michael@0 164 this.namespace = namespace;
michael@0 165 this.id = id;
michael@0 166 this.delegate = delegate;
michael@0 167 }
michael@0 168
michael@0 169 @Override
michael@0 170 public String getUserAgent() {
michael@0 171 return delegate.getUserAgent();
michael@0 172 }
michael@0 173
michael@0 174 @Override
michael@0 175 public int socketTimeout() {
michael@0 176 return DEFAULT_SOCKET_TIMEOUT_MSEC;
michael@0 177 }
michael@0 178
michael@0 179 @Override
michael@0 180 public void handleHttpResponse(HttpResponse response) {
michael@0 181 final int status = response.getStatusLine().getStatusCode();
michael@0 182 switch (status) {
michael@0 183 case 200:
michael@0 184 case 201:
michael@0 185 invokeHandleSuccess(status, response);
michael@0 186 return;
michael@0 187 default:
michael@0 188 invokeHandleFailure(status, response);
michael@0 189 }
michael@0 190 }
michael@0 191
michael@0 192 protected void invokeHandleError(final Exception e) {
michael@0 193 executor.execute(new Runnable() {
michael@0 194 @Override
michael@0 195 public void run() {
michael@0 196 delegate.handleError(e);
michael@0 197 }
michael@0 198 });
michael@0 199 }
michael@0 200
michael@0 201 protected void invokeHandleFailure(final int status, final HttpResponse response) {
michael@0 202 executor.execute(new Runnable() {
michael@0 203 @Override
michael@0 204 public void run() {
michael@0 205 delegate.handleFailure(status, namespace, response);
michael@0 206 }
michael@0 207 });
michael@0 208 }
michael@0 209
michael@0 210 protected void invokeHandleSuccess(final int status, final HttpResponse response) {
michael@0 211 executor.execute(new Runnable() {
michael@0 212 @Override
michael@0 213 public void run() {
michael@0 214 delegate.handleSuccess(status, namespace, id, response);
michael@0 215 }
michael@0 216 });
michael@0 217 }
michael@0 218
michael@0 219 @Override
michael@0 220 public void handleHttpProtocolException(final ClientProtocolException e) {
michael@0 221 invokeHandleError(e);
michael@0 222 }
michael@0 223
michael@0 224 @Override
michael@0 225 public void handleHttpIOException(IOException e) {
michael@0 226 invokeHandleError(e);
michael@0 227 }
michael@0 228
michael@0 229 @Override
michael@0 230 public void handleTransportException(GeneralSecurityException e) {
michael@0 231 invokeHandleError(e);
michael@0 232 }
michael@0 233 }
michael@0 234
michael@0 235 public final class BagheeraUploadResourceDelegate extends BagheeraResourceDelegate {
michael@0 236 private static final String HEADER_OBSOLETE_DOCUMENT = "X-Obsolete-Document";
michael@0 237 private static final String COMPRESSED_CONTENT_TYPE = "application/json+zlib; charset=utf-8";
michael@0 238 protected final Collection<String> obsoleteDocumentIDs;
michael@0 239
michael@0 240 public BagheeraUploadResourceDelegate(Resource resource,
michael@0 241 String namespace,
michael@0 242 String id,
michael@0 243 Collection<String> obsoleteDocumentIDs,
michael@0 244 BagheeraRequestDelegate delegate) {
michael@0 245 super(resource, namespace, id, delegate);
michael@0 246 this.obsoleteDocumentIDs = obsoleteDocumentIDs;
michael@0 247 }
michael@0 248
michael@0 249 @Override
michael@0 250 public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
michael@0 251 super.addHeaders(request, client);
michael@0 252 request.setHeader(HTTP.CONTENT_TYPE, COMPRESSED_CONTENT_TYPE);
michael@0 253 if (this.obsoleteDocumentIDs != null && this.obsoleteDocumentIDs.size() > 0) {
michael@0 254 request.addHeader(HEADER_OBSOLETE_DOCUMENT, Utils.toCommaSeparatedString(this.obsoleteDocumentIDs));
michael@0 255 }
michael@0 256 }
michael@0 257 }
michael@0 258 }

mercurial