Bug 1250782 - ResourceDelegate#handleSuccess now gives access to a JsonArray body. r=mcomella
authorEdouard Oger <eoger@fastmail.com>
Wed, 18 May 2016 12:32:55 -0700
changeset 304069 346623206d14008a0877ee57f3a2a4e396386da6
parent 304068 ec25edc09a602ae0abf6b957d603018598e3ef7b
child 304070 985cadcc9ed5a98f520e6988f556dadbb26266a9
push id30411
push userkwierso@gmail.com
push dateFri, 08 Jul 2016 00:26:45 +0000
treeherdermozilla-central@23dc78b7b57e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcomella
bugs1250782
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1250782 - ResourceDelegate#handleSuccess now gives access to a JsonArray body. r=mcomella MozReview-Commit-ID: HbRgCaMPZY7
mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
@@ -1,14 +1,15 @@
 /* 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.fxa;
 
+import org.json.simple.JSONArray;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
@@ -163,52 +164,66 @@ public class FxAccountClient20 implement
     executor.execute(new Runnable() {
       @Override
       public void run() {
         delegate.handleError(e);
       }
     });
   }
 
+  enum ResponseType {
+    JSON_ARRAY,
+    JSON_OBJECT
+  }
+
   /**
    * Translate resource callbacks into request callbacks invoked on the provided
    * executor.
    * <p>
    * Override <code>handleSuccess</code> to parse the body of the resource
    * request and call the request callback. <code>handleSuccess</code> is
    * invoked via the executor, so you don't need to delegate further.
    */
   protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
-    protected abstract void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body);
+
+    protected void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body) throws Exception {
+      throw new UnsupportedOperationException();
+    }
+
+    protected void handleSuccess(final int status, HttpResponse response, final JSONArray body) throws Exception {
+      throw new UnsupportedOperationException();
+    }
 
     protected final RequestDelegate<T> delegate;
 
     protected final byte[] tokenId;
     protected final byte[] reqHMACKey;
     protected final SkewHandler skewHandler;
+    protected final ResponseType responseType;
 
     /**
      * Create a delegate for an un-authenticated resource.
      */
-    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate) {
-      this(resource, delegate, null, null);
+    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, ResponseType responseType) {
+      this(resource, delegate, responseType, null, null);
     }
 
     /**
      * Create a delegate for a Hawk-authenticated resource.
      * <p>
      * Every Hawk request that encloses an entity (PATCH, POST, and PUT) will
      * include the payload verification hash.
      */
-    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, final byte[] tokenId, final byte[] reqHMACKey) {
+    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, ResponseType responseType, final byte[] tokenId, final byte[] reqHMACKey) {
       super(resource);
       this.delegate = delegate;
       this.reqHMACKey = reqHMACKey;
       this.tokenId = tokenId;
       this.skewHandler = SkewHandler.getSkewHandlerForResource(resource);
+      this.responseType = responseType;
     }
 
     @Override
     public AuthHeaderProvider getAuthHeaderProvider() {
       if (tokenId != null && reqHMACKey != null) {
         // We always include the payload verification hash for FxA Hawk-authenticated requests.
         final boolean includePayloadVerificationHash = true;
         return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, includePayloadVerificationHash, skewHandler.getSkewInSeconds());
@@ -245,18 +260,24 @@ public class FxAccountClient20 implement
       });
     }
 
     protected void invokeHandleSuccess(final int status, final HttpResponse response) {
       executor.execute(new Runnable() {
         @Override
         public void run() {
           try {
-            ExtendedJSONObject body = new SyncResponse(response).jsonObjectBody();
-            ResourceDelegate.this.handleSuccess(status, response, body);
+            SyncResponse syncResponse = new SyncResponse(response);
+            if (responseType == ResponseType.JSON_ARRAY) {
+              JSONArray body = syncResponse.jsonArrayBody();
+              ResourceDelegate.this.handleSuccess(status, response, body);
+            } else {
+              ExtendedJSONObject body = syncResponse.jsonObjectBody();
+              ResourceDelegate.this.handleSuccess(status, response, body);
+            }
           } catch (Exception e) {
             delegate.handleError(e);
           }
         }
       });
     }
 
     @Override
@@ -408,29 +429,23 @@ public class FxAccountClient20 implement
     BaseResource resource;
     try {
       resource = getBaseResource("account/keys");
     } catch (URISyntaxException | UnsupportedEncodingException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, tokenId, reqHMACKey) {
+    resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
       @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          byte[] kA = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
-          byte[] wrapkB = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
-          unbundleBody(body, requestKey, FxAccountUtils.KW("account/keys"), kA, wrapkB);
-          delegate.handleSuccess(new TwoKeys(kA, wrapkB));
-          return;
-        } catch (Exception e) {
-          delegate.handleError(e);
-          return;
-        }
+      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
+        byte[] kA = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
+        byte[] wrapkB = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
+        unbundleBody(body, requestKey, FxAccountUtils.KW("account/keys"), kA, wrapkB);
+        delegate.handleSuccess(new TwoKeys(kA, wrapkB));
       }
     };
     resource.get();
   }
 
   /**
    * Thin container for status response.
    */
@@ -470,30 +485,24 @@ public class FxAccountClient20 implement
     BaseResource resource;
     try {
       resource = getBaseResource("recovery_email/status");
     } catch (URISyntaxException | UnsupportedEncodingException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<StatusResponse>(resource, delegate, tokenId, reqHMACKey) {
+    resource.delegate = new ResourceDelegate<StatusResponse>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
       @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          String[] requiredStringFields = new String[] { JSON_KEY_EMAIL };
-          body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
-          String email = body.getString(JSON_KEY_EMAIL);
-          Boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
-          delegate.handleSuccess(new StatusResponse(email, verified));
-          return;
-        } catch (Exception e) {
-          delegate.handleError(e);
-          return;
-        }
+      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
+        String[] requiredStringFields = new String[] { JSON_KEY_EMAIL };
+        body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
+        String email = body.getString(JSON_KEY_EMAIL);
+        Boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
+        delegate.handleSuccess(new StatusResponse(email, verified));
       }
     };
     resource.get();
   }
 
   @SuppressWarnings("unchecked")
   public void sign(final byte[] sessionToken, final ExtendedJSONObject publicKey, long durationInMilliseconds, final RequestDelegate<String> delegate) {
     final ExtendedJSONObject body = new ExtendedJSONObject();
@@ -512,19 +521,19 @@ public class FxAccountClient20 implement
     BaseResource resource;
     try {
       resource = getBaseResource("certificate/sign");
     } catch (URISyntaxException | UnsupportedEncodingException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<String>(resource, delegate, tokenId, reqHMACKey) {
+    resource.delegate = new ResourceDelegate<String>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
       @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
+      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
         String cert = body.getString("cert");
         if (cert == null) {
           delegate.handleError(new FxAccountClientException("cert must be a non-null string"));
           return;
         }
         delegate.handleSuccess(cert);
       }
     };
@@ -575,41 +584,35 @@ public class FxAccountClient20 implement
       }
       resource = getBaseResource(path, modifiedParameters);
       body = new FxAccount20LoginDelegate(emailUTF8, quickStretchedPW).getCreateBody();
     } catch (Exception e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
+    resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
       @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
-          body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
+      public void handleSuccess(int status, HttpResponse response,  ExtendedJSONObject body) throws Exception {
+        final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
+        body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
 
-          final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
-          body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class);
+        final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
+        body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class);
 
-          String uid = body.getString(JSON_KEY_UID);
-          boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
-          byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
-          byte[] keyFetchToken = null;
-          if (getKeys) {
-            keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
-          }
-          LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
+        String uid = body.getString(JSON_KEY_UID);
+        boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
+        byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
+        byte[] keyFetchToken = null;
+        if (getKeys) {
+          keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
+        }
+        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
 
-          delegate.handleSuccess(loginResponse);
-          return;
-        } catch (Exception e) {
-          delegate.handleError(e);
-          return;
-        }
+        delegate.handleSuccess(loginResponse);
       }
     };
 
     post(resource, body, delegate);
   }
 
   public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW,
                             final boolean getKeys,
@@ -630,40 +633,36 @@ public class FxAccountClient20 implement
       resource = getBaseResource(path, modifiedParameters);
       body = new FxAccount20CreateDelegate(emailUTF8, quickStretchedPW, preVerified).getCreateBody();
     } catch (Exception e) {
       invokeHandleError(delegate, e);
       return;
     }
 
     // This is very similar to login, except verified is not required.
-    resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
+    resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
       @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
-          body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
+      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
+        final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
+        body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
 
-          String uid = body.getString(JSON_KEY_UID);
-          boolean verified = false; // In production, we're definitely not verified immediately upon creation.
-          Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED);
-          if (tempVerified != null) {
-            verified = tempVerified;
-          }
-          byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
-          byte[] keyFetchToken = null;
-          if (getKeys) {
-            keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
-          }
-          LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
+        String uid = body.getString(JSON_KEY_UID);
+        boolean verified = false; // In production, we're definitely not verified immediately upon creation.
+        Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED);
+        if (tempVerified != null) {
+          verified = tempVerified;
+        }
+        byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
+        byte[] keyFetchToken = null;
+        if (getKeys) {
+          keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
+        }
+        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
 
-          delegate.handleSuccess(loginResponse);
-        } catch (Exception e) {
-          delegate.handleError(e);
-        }
+        delegate.handleSuccess(loginResponse);
       }
     };
 
     post(resource, body, delegate);
   }
 
   /**
    * We want users to be able to enter their email address case-insensitively.