diff --git a/Parse/build.gradle b/Parse/build.gradle index 4f1733fdf..ed946e910 100644 --- a/Parse/build.gradle +++ b/Parse/build.gradle @@ -39,14 +39,22 @@ android { } } -ext.okhttpVersion = '3.6.0' +ext { + okhttpVersion = '3.6.0' +} + dependencies { + compile "com.android.support:support-annotations:$supportLibVersion" compile 'com.parse.bolts:bolts-tasks:1.4.0' compile "com.squareup.okhttp3:okhttp:$okhttpVersion" provided 'com.facebook.stetho:stetho:1.4.2' + //Be aware, tests fail on 3.3.2 Wait to update until + //java.lang.NoClassDefFoundError: android/content/pm/VersionedPackage + //issue is fixed in Robolectric + //https://github.com/robolectric/robolectric/issues/2562 testCompile 'org.robolectric:robolectric:3.3' - testCompile 'org.skyscreamer:jsonassert:1.4.0' + testCompile 'org.skyscreamer:jsonassert:1.5.0' testCompile 'org.mockito:mockito-core:1.10.19' testCompile "com.squareup.okhttp3:mockwebserver:$okhttpVersion" } diff --git a/Parse/src/main/java/com/parse/Parse.java b/Parse/src/main/java/com/parse/Parse.java index 017cb143b..077f91ef0 100644 --- a/Parse/src/main/java/com/parse/Parse.java +++ b/Parse/src/main/java/com/parse/Parse.java @@ -14,8 +14,6 @@ import android.os.Bundle; import android.util.Log; -import com.parse.http.ParseNetworkInterceptor; - import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -24,9 +22,6 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -34,6 +29,7 @@ import bolts.Continuation; import bolts.Task; +import okhttp3.OkHttpClient; /** * The {@code Parse} class contains static functions that handle global configuration for the Parse @@ -53,14 +49,14 @@ public static final class Builder { private String clientKey; private String server; private boolean localDataStoreEnabled; - private List interceptors; + private OkHttpClient.Builder clientBuilder; /** * Initialize a bulider with a given context. - * + *

* This context will then be passed through to the rest of the Parse SDK for use during * initialization. - * + *

*

* You may define {@code com.parse.SERVER_URL}, {@code com.parse.APPLICATION_ID} and (optional) {@code com.parse.CLIENT_KEY} * {@code meta-data} in your {@code AndroidManifest.xml}: @@ -86,7 +82,7 @@ public static final class Builder { * </manifest> * *

- * + *

* This will cause the values for {@code server}, {@code applicationId} and {@code clientKey} to be set to * those defined in your manifest. * @@ -110,7 +106,7 @@ public Builder(Context context) { /** * Set the application id to be used by Parse. - * + *

* This method is only required if you intend to use a different {@code applicationId} than * is defined by {@code com.parse.APPLICATION_ID} in your {@code AndroidManifest.xml}. * @@ -124,7 +120,7 @@ public Builder applicationId(String applicationId) { /** * Set the client key to be used by Parse. - * + *

* This method is only required if you intend to use a different {@code clientKey} than * is defined by {@code com.parse.CLIENT_KEY} in your {@code AndroidManifest.xml}. * @@ -139,9 +135,6 @@ public Builder clientKey(String clientKey) { /** * Set the server URL to be used by Parse. * - * This method is only required if you intend to use a different API server than the one at - * api.parse.com. - * * @param server The server URL to set. * @return The same builder, for easy chaining. */ @@ -157,23 +150,10 @@ public Builder server(String server) { return this; } - /** - * Add a {@link ParseNetworkInterceptor}. - * - * @param interceptor The interceptor to add. - * @return The same builder, for easy chaining. - */ - public Builder addNetworkInterceptor(ParseNetworkInterceptor interceptor) { - if (interceptors == null) { - interceptors = new ArrayList<>(); - } - interceptors.add(interceptor); - return this; - } - /** * Enable pinning in your application. This must be called before your application can use * pinning. + * * @return The same builder, for easy chaining. */ public Builder enableLocalDataStore() { @@ -181,26 +161,27 @@ public Builder enableLocalDataStore() { return this; } - /* package for tests */ Builder setNetworkInterceptors(Collection interceptors) { - if (this.interceptors == null) { - this.interceptors = new ArrayList<>(); - } else { - this.interceptors.clear(); - } - - if (interceptors != null) { - this.interceptors.addAll(interceptors); - } + private Builder setLocalDatastoreEnabled(boolean enabled) { + localDataStoreEnabled = enabled; return this; } - private Builder setLocalDatastoreEnabled(boolean enabled) { - localDataStoreEnabled = enabled; + /** + * Set the {@link okhttp3.OkHttpClient.Builder} to use when communicating with the Parse + * REST API + *

+ * + * @param builder The client builder, which will be modified for compatibility + * @return The same builder, for easy chaining. + */ + public Builder clientBuilder(OkHttpClient.Builder builder) { + clientBuilder = builder; return this; } /** * Construct this builder into a concrete {@code Configuration} instance. + * * @return A constructed {@code Configuration} object. */ public Configuration build() { @@ -208,12 +189,13 @@ public Configuration build() { } } - /* package for tests */ final Context context; - /* package for tests */ final String applicationId; - /* package for tests */ final String clientKey; - /* package for tests */ final String server; - /* package for tests */ final boolean localDataStoreEnabled; - /* package for tests */ final List interceptors; + final Context context; + final String applicationId; + final String clientKey; + final String server; + final boolean localDataStoreEnabled; + final OkHttpClient.Builder clientBuilder; + private Configuration(Builder builder) { this.context = builder.context; @@ -221,9 +203,7 @@ private Configuration(Builder builder) { this.clientKey = builder.clientKey; this.server = builder.server; this.localDataStoreEnabled = builder.localDataStoreEnabled; - this.interceptors = builder.interceptors != null ? - Collections.unmodifiableList(new ArrayList<>(builder.interceptors)) : - null; + this.clientBuilder = builder.clientBuilder; } } @@ -232,7 +212,7 @@ private Configuration(Builder builder) { private static final String PARSE_CLIENT_KEY = "com.parse.CLIENT_KEY"; private static final Object MUTEX = new Object(); - /* package */ static ParseEventuallyQueue eventuallyQueue = null; + static ParseEventuallyQueue eventuallyQueue = null; //region LDS @@ -253,34 +233,33 @@ private Configuration(Builder builder) { * } * * - * @param context - * The active {@link Context} for your application. + * @param context The active {@link Context} for your application. */ public static void enableLocalDatastore(Context context) { if (isInitialized()) { throw new IllegalStateException("`Parse#enableLocalDatastore(Context)` must be invoked " + - "before `Parse#initialize(Context)`"); + "before `Parse#initialize(Context)`"); } isLocalDatastoreEnabled = true; } - /* package for tests */ static void disableLocalDatastore() { + static void disableLocalDatastore() { setLocalDatastore(null); // We need to re-register ParseCurrentInstallationController otherwise it is still offline // controller ParseCorePlugins.getInstance().reset(); } - /* package */ static OfflineStore getLocalDatastore() { + static OfflineStore getLocalDatastore() { return offlineStore; } - /* package for tests */ static void setLocalDatastore(OfflineStore offlineStore) { + static void setLocalDatastore(OfflineStore offlineStore) { Parse.isLocalDatastoreEnabled = offlineStore != null; Parse.offlineStore = offlineStore; } - /* package */ static boolean isLocalDatastoreEnabled() { + static boolean isLocalDatastoreEnabled() { return isLocalDatastoreEnabled; } @@ -325,27 +304,27 @@ public static void enableLocalDatastore(Context context) { * } * * - * @param context - * The active {@link Context} for your application. + * @param context The active {@link Context} for your application. */ public static void initialize(Context context) { Configuration.Builder builder = new Configuration.Builder(context); if (builder.server == null) { throw new RuntimeException("ServerUrl not defined. " + - "You must provide ServerUrl in AndroidManifest.xml.\n" + - "\" />"); - } if (builder.applicationId == null) { - throw new RuntimeException("ApplicationId not defined. " + - "You must provide ApplicationId in AndroidManifest.xml.\n" + - "\" />"); + "You must provide ServerUrl in AndroidManifest.xml.\n" + + "\" />"); } - initialize(builder.setNetworkInterceptors(interceptors) - .setLocalDatastoreEnabled(isLocalDatastoreEnabled) - .build() + if (builder.applicationId == null) { + throw new RuntimeException("ApplicationId not defined. " + + "You must provide ApplicationId in AndroidManifest.xml.\n" + + "\" />"); + } + initialize(builder + .setLocalDatastoreEnabled(isLocalDatastoreEnabled) + .build() ); } @@ -367,21 +346,17 @@ public static void initialize(Context context) { * } * } * - * - * @param context - * The active {@link Context} for your application. - * @param applicationId - * The application id provided in the Parse dashboard. - * @param clientKey - * The client key provided in the Parse dashboard. + * + * @param context The active {@link Context} for your application. + * @param applicationId The application id provided in the Parse dashboard. + * @param clientKey The client key provided in the Parse dashboard. */ public static void initialize(Context context, String applicationId, String clientKey) { initialize(new Configuration.Builder(context) - .applicationId(applicationId) - .clientKey(clientKey) - .setNetworkInterceptors(interceptors) - .setLocalDatastoreEnabled(isLocalDatastoreEnabled) - .build() + .applicationId(applicationId) + .clientKey(clientKey) + .setLocalDatastoreEnabled(isLocalDatastoreEnabled) + .build() ); } @@ -390,7 +365,7 @@ public static void initialize(Configuration configuration) { // isLocalDataStoreEnabled() to perform additional behavior. isLocalDatastoreEnabled = configuration.localDataStoreEnabled; - ParsePlugins.Android.initialize(configuration.context, configuration.applicationId, configuration.clientKey); + ParsePlugins.Android.initialize(configuration.context, configuration); try { ParseRESTCommand.server = new URL(configuration.server); @@ -402,10 +377,6 @@ public static void initialize(Configuration configuration) { ParseHttpClient.setKeepAlive(true); ParseHttpClient.setMaxConnections(20); - // If we have interceptors in list, we have to initialize all http clients and add interceptors - if (configuration.interceptors != null && configuration.interceptors.size() > 0) { - initializeParseHttpClientsWithParseNetworkInterceptors(configuration.interceptors); - } ParseObject.registerParseSubclasses(); @@ -432,7 +403,7 @@ public Void call() throws Exception { if (!allParsePushIntentReceiversInternal()) { throw new SecurityException("To prevent external tampering to your app's notifications, " + "all receivers registered to handle the following actions must have " + - "their exported attributes set to false: com.parse.push.intent.RECEIVE, "+ + "their exported attributes set to false: com.parse.push.intent.RECEIVE, " + "com.parse.push.intent.OPEN, com.parse.push.intent.DELETE"); } @@ -465,7 +436,7 @@ public Void then(Task task) throws Exception { } } - /* package */ static void destroy() { + static void destroy() { ParseEventuallyQueue queue; synchronized (MUTEX) { queue = eventuallyQueue; @@ -482,7 +453,7 @@ public Void then(Task task) throws Exception { /** * @return {@code True} if {@link #initialize} has been called, otherwise {@code false}. */ - /* package */ static boolean isInitialized() { + static boolean isInitialized() { return ParsePlugins.get() != null; } @@ -503,9 +474,9 @@ static Context getApplicationContext() { */ private static boolean allParsePushIntentReceiversInternal() { List intentReceivers = ManifestInfo.getIntentReceivers( - ParsePushBroadcastReceiver.ACTION_PUSH_RECEIVE, - ParsePushBroadcastReceiver.ACTION_PUSH_DELETE, - ParsePushBroadcastReceiver.ACTION_PUSH_OPEN); + ParsePushBroadcastReceiver.ACTION_PUSH_RECEIVE, + ParsePushBroadcastReceiver.ACTION_PUSH_DELETE, + ParsePushBroadcastReceiver.ACTION_PUSH_OPEN); for (ResolveInfo resolveInfo : intentReceivers) { if (resolveInfo.activityInfo.exported) { @@ -520,15 +491,15 @@ private static boolean allParsePushIntentReceiversInternal() { * instead. */ @Deprecated - /* package */ static File getParseDir() { + static File getParseDir() { return ParsePlugins.get().getParseDir(); } - /* package */ static File getParseCacheDir() { + static File getParseCacheDir() { return ParsePlugins.get().getCacheDir(); } - /* package */ static File getParseCacheDir(String subDir) { + static File getParseCacheDir(String subDir) { synchronized (MUTEX) { File dir = new File(getParseCacheDir(), subDir); if (!dir.exists()) { @@ -538,11 +509,11 @@ private static boolean allParsePushIntentReceiversInternal() { } } - /* package */ static File getParseFilesDir() { + static File getParseFilesDir() { return ParsePlugins.get().getFilesDir(); } - /* package */ static File getParseFilesDir(String subDir) { + static File getParseFilesDir(String subDir) { synchronized (MUTEX) { File dir = new File(getParseFilesDir(), subDir); if (!dir.exists()) { @@ -615,7 +586,7 @@ static void checkCacheApplicationId() { * ParseCommandCache is instantiated, it will begin running its run loop, which will start by * processing any commands already stored in the on-disk queue. */ - /* package */ static ParseEventuallyQueue getEventuallyQueue() { + static ParseEventuallyQueue getEventuallyQueue() { Context context = ParsePlugins.Android.get().applicationContext(); return getEventuallyQueue(context); } @@ -624,13 +595,13 @@ private static ParseEventuallyQueue getEventuallyQueue(Context context) { synchronized (MUTEX) { boolean isLocalDatastoreEnabled = Parse.isLocalDatastoreEnabled(); if (eventuallyQueue == null - || (isLocalDatastoreEnabled && eventuallyQueue instanceof ParseCommandCache) - || (!isLocalDatastoreEnabled && eventuallyQueue instanceof ParsePinningEventuallyQueue)) { + || (isLocalDatastoreEnabled && eventuallyQueue instanceof ParseCommandCache) + || (!isLocalDatastoreEnabled && eventuallyQueue instanceof ParsePinningEventuallyQueue)) { checkContext(); ParseHttpClient httpClient = ParsePlugins.get().restClient(); eventuallyQueue = isLocalDatastoreEnabled - ? new ParsePinningEventuallyQueue(context, httpClient) - : new ParseCommandCache(context, httpClient); + ? new ParsePinningEventuallyQueue(context, httpClient) + : new ParseCommandCache(context, httpClient); // We still need to clear out the old command cache even if we're using Pinning in case // anything is left over when the user upgraded. Checking number of pending and then @@ -646,34 +617,34 @@ private static ParseEventuallyQueue getEventuallyQueue(Context context) { static void checkInit() { if (ParsePlugins.get() == null) { throw new RuntimeException("You must call Parse.initialize(Context)" - + " before using the Parse library."); + + " before using the Parse library."); } if (ParsePlugins.get().applicationId() == null) { throw new RuntimeException("applicationId is null. " - + "You must call Parse.initialize(Context)" - + " before using the Parse library."); + + "You must call Parse.initialize(Context)" + + " before using the Parse library."); } } static void checkContext() { if (ParsePlugins.Android.get().applicationContext() == null) { throw new RuntimeException("applicationContext is null. " - + "You must call Parse.initialize(Context)" - + " before using the Parse library."); + + "You must call Parse.initialize(Context)" + + " before using the Parse library."); } } static boolean hasPermission(String permission) { return (getApplicationContext().checkCallingOrSelfPermission(permission) == - PackageManager.PERMISSION_GRANTED); + PackageManager.PERMISSION_GRANTED); } static void requirePermission(String permission) { if (!hasPermission(permission)) { throw new IllegalStateException( - "To use this functionality, add this to your AndroidManifest.xml:\n" - + ""); + "To use this functionality, add this to your AndroidManifest.xml:\n" + + ""); } } @@ -688,10 +659,10 @@ static void requirePermission(String permission) { * * @param listener the listener to register */ - /* package */ static void registerParseCallbacks(ParseCallbacks listener) { + static void registerParseCallbacks(ParseCallbacks listener) { if (isInitialized()) { throw new IllegalStateException( - "You must register callbacks before Parse.initialize(Context)"); + "You must register callbacks before Parse.initialize(Context)"); } synchronized (MUTEX_CALLBACKS) { @@ -707,7 +678,7 @@ static void requirePermission(String permission) { * * @param listener the listener to register */ - /* package */ static void unregisterParseCallbacks(ParseCallbacks listener) { + static void unregisterParseCallbacks(ParseCallbacks listener) { synchronized (MUTEX_CALLBACKS) { if (callbacks == null) { return; @@ -739,8 +710,8 @@ private static ParseCallbacks[] collectParseCallbacks() { return callbacks; } - /* package */ interface ParseCallbacks { - public void onParseInitialized(); + interface ParseCallbacks { + void onParseInitialized(); } //endregion @@ -768,8 +739,7 @@ private static ParseCallbacks[] collectParseCallbacks() { *

  • {@link #LOG_LEVEL_NONE}
  • * * - * @param logLevel - * The level of logcat logging that Parse should do. + * @param logLevel The level of logcat logging that Parse should do. */ public static void setLogLevel(int logLevel) { PLog.setLogLevel(logLevel); @@ -789,73 +759,7 @@ private Parse() { throw new AssertionError(); } - private static List interceptors; - - // Initialize all necessary http clients and add interceptors to these http clients - private static void initializeParseHttpClientsWithParseNetworkInterceptors(List interceptors) { - // This means developers have not called addInterceptor method so we should do nothing. - if (interceptors == null) { - return; - } - - List clients = new ArrayList<>(); - - // Rest http client - clients.add(ParsePlugins.get().restClient()); - // AWS http client - clients.add(ParseCorePlugins.getInstance().getFileController().awsClient()); - - // Add interceptors to http clients - for (ParseHttpClient parseHttpClient : clients) { - // We need to add the decompress interceptor before the external interceptors to return - // a decompressed response to Parse. - parseHttpClient.addInternalInterceptor(new ParseDecompressInterceptor()); - for (ParseNetworkInterceptor interceptor : interceptors) { - parseHttpClient.addExternalInterceptor(interceptor); - } - } - } - - - /** - * Add a {@link ParseNetworkInterceptor}. You must invoke - * {@code addParseNetworkInterceptor(ParseNetworkInterceptor)} before - * {@link #initialize(Context)}. You can add multiple {@link ParseNetworkInterceptor}. - * - * @param interceptor - * {@link ParseNetworkInterceptor} to be added. - */ - public static void addParseNetworkInterceptor(ParseNetworkInterceptor interceptor) { - if (isInitialized()) { - throw new IllegalStateException("`Parse#addParseNetworkInterceptor(ParseNetworkInterceptor)`" - + " must be invoked before `Parse#initialize(Context)`"); - } - if (interceptors == null) { - interceptors = new ArrayList<>(); - } - interceptors.add(interceptor); - } - - /** - * Remove a given {@link ParseNetworkInterceptor}. You must invoke - * {@code removeParseNetworkInterceptor(ParseNetworkInterceptor)} before - * {@link #initialize(Context)}. - * - * @param interceptor - * {@link ParseNetworkInterceptor} to be removed. - */ - public static void removeParseNetworkInterceptor(ParseNetworkInterceptor interceptor) { - if (isInitialized()) { - throw new IllegalStateException("`Parse#addParseNetworkInterceptor(ParseNetworkInterceptor)`" - + " must be invoked before `Parse#initialize(Context)`"); - } - if (interceptors == null) { - return; - } - interceptors.remove(interceptor); - } - - /* package */ static String externalVersionName() { + static String externalVersionName() { return "a" + ParseObject.VERSION_NAME; } } diff --git a/Parse/src/main/java/com/parse/ParseDecompressInterceptor.java b/Parse/src/main/java/com/parse/ParseDecompressInterceptor.java deleted file mode 100644 index 1303d8703..000000000 --- a/Parse/src/main/java/com/parse/ParseDecompressInterceptor.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2015-present, Parse, LLC. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.parse; - -import com.parse.http.ParseHttpRequest; -import com.parse.http.ParseHttpResponse; -import com.parse.http.ParseNetworkInterceptor; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.GZIPInputStream; - -/** package */ class ParseDecompressInterceptor implements ParseNetworkInterceptor { - - private static final String CONTENT_ENCODING_HEADER = "Content-Encoding"; - private static final String CONTENT_LENGTH_HEADER = "Content-Length"; - private static final String GZIP_ENCODING = "gzip"; - - @Override - public ParseHttpResponse intercept(Chain chain) throws IOException { - ParseHttpRequest request = chain.getRequest(); - ParseHttpResponse response = chain.proceed(request); - // If the response is gziped, we need to decompress the stream and remove the gzip header. - if (GZIP_ENCODING.equalsIgnoreCase(response.getHeader(CONTENT_ENCODING_HEADER))) { - Map newHeaders = new HashMap<>(response.getAllHeaders()); - newHeaders.remove(CONTENT_ENCODING_HEADER); - // Since before we decompress the stream, we can not know the actual length of the stream. - // In this situation, we follow the OkHttp library, set the content-length of the response - // to -1 - newHeaders.put(CONTENT_LENGTH_HEADER, "-1"); - // TODO(mengyan): Add builder constructor based on an existing ParseHttpResponse - response = new ParseHttpResponse.Builder(response) - .setTotalSize(-1) - .setHeaders(newHeaders) - .setContent(new GZIPInputStream(response.getContent())) - .build(); - } - return response; - } -} - diff --git a/Parse/src/main/java/com/parse/ParseFileController.java b/Parse/src/main/java/com/parse/ParseFileController.java index 843145650..984ca7272 100644 --- a/Parse/src/main/java/com/parse/ParseFileController.java +++ b/Parse/src/main/java/com/parse/ParseFileController.java @@ -27,7 +27,7 @@ private final ParseHttpClient restClient; private final File cachePath; - private ParseHttpClient awsClient; + private ParseHttpClient fileClient; public ParseFileController(ParseHttpClient restClient, File cachePath) { this.restClient = restClient; @@ -35,21 +35,21 @@ public ParseFileController(ParseHttpClient restClient, File cachePath) { } /** - * Gets the AWS http client if exists, otherwise lazily creates since developers might not always + * Gets the file http client if exists, otherwise lazily creates since developers might not always * use our download mechanism. */ - /* package */ ParseHttpClient awsClient() { + /* package */ ParseHttpClient fileClient() { synchronized (lock) { - if (awsClient == null) { - awsClient = ParsePlugins.get().newHttpClient(); + if (fileClient == null) { + fileClient = ParsePlugins.get().fileClient(); } - return awsClient; + return fileClient; } } - /* package for tests */ ParseFileController awsClient(ParseHttpClient awsClient) { + /* package for tests */ ParseFileController fileClient(ParseHttpClient fileClient) { synchronized (lock) { - this.awsClient = awsClient; + this.fileClient = fileClient; } return this; } @@ -206,12 +206,12 @@ public Task then(Task task) throws Exception { final File tempFile = getTempFile(state); // network - final ParseAWSRequest request = - new ParseAWSRequest(ParseHttpRequest.Method.GET, state.url(), tempFile); + final ParseFileRequest request = + new ParseFileRequest(ParseHttpRequest.Method.GET, state.url(), tempFile); // We do not need to delete the temp file since we always try to overwrite it return request.executeAsync( - awsClient(), + fileClient(), null, downloadProgressCallback, cancellationToken).continueWithTask(new Continuation>() { diff --git a/Parse/src/main/java/com/parse/ParseAWSRequest.java b/Parse/src/main/java/com/parse/ParseFileRequest.java similarity index 91% rename from Parse/src/main/java/com/parse/ParseAWSRequest.java rename to Parse/src/main/java/com/parse/ParseFileRequest.java index a033555f1..f5d94945d 100644 --- a/Parse/src/main/java/com/parse/ParseAWSRequest.java +++ b/Parse/src/main/java/com/parse/ParseFileRequest.java @@ -22,12 +22,12 @@ * Request returns a byte array of the response and provides a callback the progress of the data * read from the network. */ -/** package */ class ParseAWSRequest extends ParseRequest { +/** package */ class ParseFileRequest extends ParseRequest { // The temp file is used to save the ParseFile content when we fetch it from server private final File tempFile; - public ParseAWSRequest(ParseHttpRequest.Method method, String url, File tempFile) { + public ParseFileRequest(ParseHttpRequest.Method method, String url, File tempFile) { super(method, url); this.tempFile = tempFile; } @@ -41,7 +41,7 @@ protected Task onResponseAsync(final ParseHttpResponse response, } else { String action = method == ParseHttpRequest.Method.GET ? "Download from" : "Upload to"; return Task.forError(new ParseException(ParseException.CONNECTION_FAILED, String.format( - "%s S3 failed. %s", action, response.getReasonPhrase()))); + "%s file server failed. %s", action, response.getReasonPhrase()))); } if (method != ParseHttpRequest.Method.GET) { diff --git a/Parse/src/main/java/com/parse/ParseHttpClient.java b/Parse/src/main/java/com/parse/ParseHttpClient.java index c2f17484b..2592c6bb1 100644 --- a/Parse/src/main/java/com/parse/ParseHttpClient.java +++ b/Parse/src/main/java/com/parse/ParseHttpClient.java @@ -8,41 +8,42 @@ */ package com.parse; -import android.net.SSLSessionCache; +import android.support.annotation.Nullable; +import com.parse.http.ParseHttpBody; import com.parse.http.ParseHttpRequest; import com.parse.http.ParseHttpResponse; -import com.parse.http.ParseNetworkInterceptor; import java.io.IOException; -import java.util.ArrayList; +import java.io.InputStream; +import java.util.HashMap; import java.util.List; - -/** - * The base class of a httpclient. It takes an http request, sends it to the server - * and gets response. It can be implemented by different http library such as Apache http, - * Android URLConnection, Square OKHttp and so on. - */ -/** package */ abstract class ParseHttpClient { - private static final String TAG = "com.parse.ParseHttpClient"; - - private static final String URLCONNECTION_NAME = "net.java.URLConnection"; - private static final String OKHTTP_NAME = "com.squareup.okhttp3"; - +import java.util.Map; + +import okhttp3.Call; +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.BufferedSink; + +class ParseHttpClient { + + private final static String OKHTTP_GET = "GET"; + private final static String OKHTTP_POST = "POST"; + private final static String OKHTTP_PUT = "PUT"; + private final static String OKHTTP_DELETE = "DELETE"; + + public static ParseHttpClient createClient(@Nullable OkHttpClient.Builder builder) { + return new ParseHttpClient(builder); + } private static final String MAX_CONNECTIONS_PROPERTY_NAME = "http.maxConnections"; private static final String KEEP_ALIVE_PROPERTY_NAME = "http.keepAlive"; - public static ParseHttpClient createClient(int socketOperationTimeout, - SSLSessionCache sslSessionCache) { - String httpClientLibraryName; - ParseHttpClient httpClient; - httpClientLibraryName = OKHTTP_NAME; - httpClient = new ParseOkHttpClient(socketOperationTimeout, sslSessionCache); - PLog.i(TAG, "Using " + httpClientLibraryName + " library for networking communication."); - return httpClient; - } - public static void setMaxConnections(int maxConnections) { if (maxConnections <= 0) { throw new IllegalArgumentException("Max connections should be large than 0"); @@ -54,98 +55,189 @@ public static void setKeepAlive(boolean isKeepAlive) { System.setProperty(KEEP_ALIVE_PROPERTY_NAME, String.valueOf(isKeepAlive)); } + private OkHttpClient okHttpClient; private boolean hasExecuted; - // There is no need to keep locks for interceptor lists since they will only be changed before - // we make network request - private List internalInterceptors; - private List externalInterceptors; + ParseHttpClient(@Nullable OkHttpClient.Builder builder) { + + if (builder == null) { + builder = new OkHttpClient.Builder(); + } + + // Don't handle redirects. We copy the setting from AndroidHttpClient. + // For detail, check https://quip.com/Px8jAxnaun2r + builder.followRedirects(false); + + okHttpClient = builder.build(); + } + + public final ParseHttpResponse execute(ParseHttpRequest request) throws IOException { + if (!hasExecuted) { + hasExecuted = true; + } + return executeInternal(request); + } + + ParseHttpResponse executeInternal(ParseHttpRequest parseRequest) throws IOException { + Request okHttpRequest = getRequest(parseRequest); + Call okHttpCall = okHttpClient.newCall(okHttpRequest); + + Response okHttpResponse = okHttpCall.execute(); + + return getResponse(okHttpResponse); + } + + ParseHttpResponse getResponse(Response okHttpResponse) + throws IOException { + // Status code + int statusCode = okHttpResponse.code(); - /* package */ abstract ParseHttpResponse executeInternal(ParseHttpRequest request) - throws IOException; + // Content + InputStream content = okHttpResponse.body().byteStream(); - /* package */ abstract LibraryRequest getRequest(ParseHttpRequest parseRequest) - throws IOException; + // Total size + int totalSize = (int) okHttpResponse.body().contentLength(); - /* package */ abstract ParseHttpResponse getResponse(LibraryResponse okHttpResponse) - throws IOException; + // Reason phrase + String reasonPhrase = okHttpResponse.message(); - /* package */ void addInternalInterceptor(ParseNetworkInterceptor interceptor) { - // If we do not have the restriction, we may have read/write conflict on the interceptorList - // and need to add lock to protect it. If in the future we need to add interceptor after - // httpclient start to execute, it is safe to remove this check and add lock. - if (hasExecuted) { - throw new IllegalStateException( - "`ParseHttpClient#addInternalInterceptor(ParseNetworkInterceptor)` can only be invoked " + - "before `ParseHttpClient` execute any request"); + // Headers + Map headers = new HashMap<>(); + for (String name : okHttpResponse.headers().names()) { + headers.put(name, okHttpResponse.header(name)); } - if (internalInterceptors == null) { - internalInterceptors = new ArrayList<>(); + // Content type + String contentType = null; + ResponseBody body = okHttpResponse.body(); + if (body != null && body.contentType() != null) { + contentType = body.contentType().toString(); } - internalInterceptors.add(interceptor); + + return new ParseHttpResponse.Builder() + .setStatusCode(statusCode) + .setContent(content) + .setTotalSize(totalSize) + .setReasonPhrase(reasonPhrase) + .setHeaders(headers) + .setContentType(contentType) + .build(); } - /* package */ void addExternalInterceptor(ParseNetworkInterceptor interceptor) { - // No need to check hasExecuted since this method will only be called before Parse.initialize() - if (externalInterceptors == null) { - externalInterceptors = new ArrayList<>(); + Request getRequest(ParseHttpRequest parseRequest) throws IOException { + Request.Builder okHttpRequestBuilder = new Request.Builder(); + ParseHttpRequest.Method method = parseRequest.getMethod(); + // Set method + switch (method) { + case GET: + okHttpRequestBuilder.get(); + break; + case DELETE: + case POST: + case PUT: + // Since we need to set body and method at the same time for DELETE, POST, PUT, we will do it in + // the following. + break; + default: + // This case will never be reached since we have already handled this case in + // ParseRequest.newRequest(). + throw new IllegalStateException("Unsupported http method " + method.toString()); + } + // Set url + okHttpRequestBuilder.url(parseRequest.getUrl()); + + // Set Header + Headers.Builder okHttpHeadersBuilder = new Headers.Builder(); + for (Map.Entry entry : parseRequest.getAllHeaders().entrySet()) { + okHttpHeadersBuilder.add(entry.getKey(), entry.getValue()); + } + // OkHttp automatically add gzip header so we do not need to deal with it + Headers okHttpHeaders = okHttpHeadersBuilder.build(); + okHttpRequestBuilder.headers(okHttpHeaders); + + // Set Body + ParseHttpBody parseBody = parseRequest.getBody(); + ParseOkHttpRequestBody okHttpRequestBody = null; + if (parseBody != null) { + okHttpRequestBody = new ParseOkHttpRequestBody(parseBody); } - externalInterceptors.add(interceptor); + switch (method) { + case PUT: + okHttpRequestBuilder.put(okHttpRequestBody); + break; + case POST: + okHttpRequestBuilder.post(okHttpRequestBody); + break; + case DELETE: + okHttpRequestBuilder.delete(okHttpRequestBody); + } + return okHttpRequestBuilder.build(); } - public final ParseHttpResponse execute(ParseHttpRequest request) throws IOException { - if (!hasExecuted) { - hasExecuted = true; + private ParseHttpRequest getParseHttpRequest(Request okHttpRequest) { + ParseHttpRequest.Builder parseRequestBuilder = new ParseHttpRequest.Builder(); + // Set method + switch (okHttpRequest.method()) { + case OKHTTP_GET: + parseRequestBuilder.setMethod(ParseHttpRequest.Method.GET); + break; + case OKHTTP_DELETE: + parseRequestBuilder.setMethod(ParseHttpRequest.Method.DELETE); + break; + case OKHTTP_POST: + parseRequestBuilder.setMethod(ParseHttpRequest.Method.POST); + break; + case OKHTTP_PUT: + parseRequestBuilder.setMethod(ParseHttpRequest.Method.PUT); + break; + default: + // This should never happen + throw new IllegalArgumentException( + "Invalid http method " + okHttpRequest.method()); } - ParseNetworkInterceptor.Chain chain = new ParseNetworkInterceptorChain(0, 0, request); - return chain.proceed(request); + + // Set url + parseRequestBuilder.setUrl(okHttpRequest.url().toString()); + + // Set Header + for (Map.Entry> entry : okHttpRequest.headers().toMultimap().entrySet()) { + parseRequestBuilder.addHeader(entry.getKey(), entry.getValue().get(0)); + } + + // Set Body + ParseOkHttpRequestBody okHttpBody = (ParseOkHttpRequestBody) okHttpRequest.body(); + if (okHttpBody != null) { + parseRequestBuilder.setBody(okHttpBody.getParseHttpBody()); + } + return parseRequestBuilder.build(); } - private class ParseNetworkInterceptorChain implements ParseNetworkInterceptor.Chain { - private final int internalIndex; - private final int externalIndex; - private final ParseHttpRequest request; + private static class ParseOkHttpRequestBody extends RequestBody { + + private ParseHttpBody parseBody; - ParseNetworkInterceptorChain(int internalIndex, int externalIndex, ParseHttpRequest request) { - this.internalIndex = internalIndex; - this.externalIndex = externalIndex; - this.request = request; + public ParseOkHttpRequestBody(ParseHttpBody parseBody) { + this.parseBody = parseBody; } @Override - public ParseHttpRequest getRequest() { - return request; + public long contentLength() throws IOException { + return parseBody.getContentLength(); } @Override - public ParseHttpResponse proceed(ParseHttpRequest request) throws IOException { - if (internalInterceptors != null && internalIndex < internalInterceptors.size()) { - // There's another internal interceptor in the chain. Call that. - ParseNetworkInterceptor.Chain chain = - new ParseNetworkInterceptorChain(internalIndex + 1, externalIndex, request); - return internalInterceptors.get(internalIndex).intercept(chain); - } - - if (externalInterceptors != null && externalIndex < externalInterceptors.size()) { - // There's another external interceptor in the chain. Call that. - ParseNetworkInterceptor.Chain chain = - new ParseNetworkInterceptorChain(internalIndex, externalIndex + 1, request); - return externalInterceptors.get(externalIndex).intercept(chain); - } - - // No more interceptors. Do HTTP. - return executeInternal(request); + public MediaType contentType() { + String contentType = parseBody.getContentType(); + return contentType == null ? null : MediaType.parse(parseBody.getContentType()); } - } - /** - * When we find developers use interceptors, since we need expose the raw - * response(ungziped response) to interceptors, we need to disable the transparent ungzip. - * - * @return {@code true} if we should disable the http library level auto decompress. - */ - /* package */ boolean disableHttpLibraryAutoDecompress() { - return externalInterceptors != null && externalInterceptors.size() > 0; + @Override + public void writeTo(BufferedSink bufferedSink) throws IOException { + parseBody.writeTo(bufferedSink.outputStream()); + } + + public ParseHttpBody getParseHttpBody() { + return parseBody; + } } } diff --git a/Parse/src/main/java/com/parse/ParseOkHttpClient.java b/Parse/src/main/java/com/parse/ParseOkHttpClient.java deleted file mode 100644 index 0e164ddd0..000000000 --- a/Parse/src/main/java/com/parse/ParseOkHttpClient.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (c) 2015-present, Parse, LLC. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -package com.parse; - -import android.net.SSLSessionCache; - -import com.parse.http.ParseHttpBody; -import com.parse.http.ParseHttpRequest; -import com.parse.http.ParseHttpResponse; -import com.parse.http.ParseNetworkInterceptor; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import bolts.Capture; -import okhttp3.Call; -import okhttp3.Headers; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.Okio; - -/** package */ class ParseOkHttpClient extends ParseHttpClient { - - private final static String OKHTTP_GET = "GET"; - private final static String OKHTTP_POST = "POST"; - private final static String OKHTTP_PUT = "PUT"; - private final static String OKHTTP_DELETE = "DELETE"; - - private OkHttpClient okHttpClient; - - public ParseOkHttpClient(int socketOperationTimeout, SSLSessionCache sslSessionCache) { - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - - builder.connectTimeout(socketOperationTimeout, TimeUnit.MILLISECONDS); - builder.readTimeout(socketOperationTimeout, TimeUnit.MILLISECONDS); - - // Don't handle redirects. We copy the setting from AndroidHttpClient. - // For detail, check https://quip.com/Px8jAxnaun2r - builder.followRedirects(false); - - okHttpClient = builder.build(); - } - - @Override - /* package */ ParseHttpResponse executeInternal(ParseHttpRequest parseRequest) throws IOException { - Request okHttpRequest = getRequest(parseRequest); - Call okHttpCall = okHttpClient.newCall(okHttpRequest); - - Response okHttpResponse = okHttpCall.execute(); - - return getResponse(okHttpResponse); - } - - @Override - /* package */ ParseHttpResponse getResponse(Response okHttpResponse) - throws IOException { - // Status code - int statusCode = okHttpResponse.code(); - - // Content - InputStream content = okHttpResponse.body().byteStream(); - - // Total size - int totalSize = (int)okHttpResponse.body().contentLength(); - - // Reason phrase - String reasonPhrase = okHttpResponse.message(); - - // Headers - Map headers = new HashMap<>(); - for (String name : okHttpResponse.headers().names()) { - headers.put(name, okHttpResponse.header(name)); - } - - // Content type - String contentType = null; - ResponseBody body = okHttpResponse.body(); - if (body != null && body.contentType() != null) { - contentType = body.contentType().toString(); - } - - return new ParseHttpResponse.Builder() - .setStatusCode(statusCode) - .setContent(content) - .setTotalSize(totalSize) - .setReasonPhrase(reasonPhrase) - .setHeaders(headers) - .setContentType(contentType) - .build(); - } - - @Override - /* package */ Request getRequest(ParseHttpRequest parseRequest) throws IOException { - Request.Builder okHttpRequestBuilder = new Request.Builder(); - ParseHttpRequest.Method method = parseRequest.getMethod(); - // Set method - switch (method) { - case GET: - okHttpRequestBuilder.get(); - break; - case DELETE: - case POST: - case PUT: - // Since we need to set body and method at the same time for DELETE, POST, PUT, we will do it in - // the following. - break; - default: - // This case will never be reached since we have already handled this case in - // ParseRequest.newRequest(). - throw new IllegalStateException("Unsupported http method " + method.toString()); - } - // Set url - okHttpRequestBuilder.url(parseRequest.getUrl()); - - // Set Header - Headers.Builder okHttpHeadersBuilder = new Headers.Builder(); - for (Map.Entry entry : parseRequest.getAllHeaders().entrySet()) { - okHttpHeadersBuilder.add(entry.getKey(), entry.getValue()); - } - // OkHttp automatically add gzip header so we do not need to deal with it - Headers okHttpHeaders = okHttpHeadersBuilder.build(); - okHttpRequestBuilder.headers(okHttpHeaders); - - // Set Body - ParseHttpBody parseBody = parseRequest.getBody(); - ParseOkHttpRequestBody okHttpRequestBody = null; - if(parseBody != null) { - okHttpRequestBody = new ParseOkHttpRequestBody(parseBody); - } - switch (method) { - case PUT: - okHttpRequestBuilder.put(okHttpRequestBody); - break; - case POST: - okHttpRequestBuilder.post(okHttpRequestBody); - break; - case DELETE: - okHttpRequestBuilder.delete(okHttpRequestBody); - } - return okHttpRequestBuilder.build(); - } - - private ParseHttpRequest getParseHttpRequest(Request okHttpRequest) { - ParseHttpRequest.Builder parseRequestBuilder = new ParseHttpRequest.Builder(); - // Set method - switch (okHttpRequest.method()) { - case OKHTTP_GET: - parseRequestBuilder.setMethod(ParseHttpRequest.Method.GET); - break; - case OKHTTP_DELETE: - parseRequestBuilder.setMethod(ParseHttpRequest.Method.DELETE); - break; - case OKHTTP_POST: - parseRequestBuilder.setMethod(ParseHttpRequest.Method.POST); - break; - case OKHTTP_PUT: - parseRequestBuilder.setMethod(ParseHttpRequest.Method.PUT); - break; - default: - // This should never happen - throw new IllegalArgumentException( - "Invalid http method " + okHttpRequest.method()); - } - - // Set url - parseRequestBuilder.setUrl(okHttpRequest.url().toString()); - - // Set Header - for (Map.Entry> entry : okHttpRequest.headers().toMultimap().entrySet()) { - parseRequestBuilder.addHeader(entry.getKey(), entry.getValue().get(0)); - } - - // Set Body - ParseOkHttpRequestBody okHttpBody = (ParseOkHttpRequestBody) okHttpRequest.body(); - if (okHttpBody != null) { - parseRequestBuilder.setBody(okHttpBody.getParseHttpBody()); - } - return parseRequestBuilder.build(); - } - - /** - * For OKHttpClient, since it does not expose any interface for us to check the raw response - * stream, we have to use OKHttp networkInterceptors. Instead of using our own interceptor list, - * we use OKHttp inner interceptor list. - * @param parseNetworkInterceptor - */ - @Override - /* package */ void addExternalInterceptor(final ParseNetworkInterceptor parseNetworkInterceptor) { - OkHttpClient.Builder builder = okHttpClient.newBuilder(); - builder.networkInterceptors().add(new Interceptor() { - @Override - public Response intercept(final Chain okHttpChain) throws IOException { - Request okHttpRequest = okHttpChain.request(); - // Transfer OkHttpRequest to ParseHttpRequest - final ParseHttpRequest parseRequest = getParseHttpRequest(okHttpRequest); - // Capture OkHttpResponse - final Capture okHttpResponseCapture = new Capture<>(); - final ParseHttpResponse parseResponse = - parseNetworkInterceptor.intercept(new ParseNetworkInterceptor.Chain() { - @Override - public ParseHttpRequest getRequest() { - return parseRequest; - } - - @Override - public ParseHttpResponse proceed(ParseHttpRequest parseRequest) throws IOException { - // Use OKHttpClient to send request - Request okHttpRequest = ParseOkHttpClient.this.getRequest(parseRequest); - Response okHttpResponse = okHttpChain.proceed(okHttpRequest); - okHttpResponseCapture.set(okHttpResponse); - return getResponse(okHttpResponse); - } - }); - final Response okHttpResponse = okHttpResponseCapture.get(); - // Ideally we should build newOkHttpResponse only based on parseResponse, however - // ParseHttpResponse does not have all the info we need to build the newOkHttpResponse, so - // we rely on the okHttpResponse to generate the builder and change the necessary info - // inside - Response.Builder newOkHttpResponseBuilder = okHttpResponse.newBuilder(); - // Set status - newOkHttpResponseBuilder - .code(parseResponse.getStatusCode()) - .message(parseResponse.getReasonPhrase()); - // Set headers - if (parseResponse.getAllHeaders() != null) { - for (Map.Entry entry : parseResponse.getAllHeaders().entrySet()) { - newOkHttpResponseBuilder.header(entry.getKey(), entry.getValue()); - } - } - // Set body - newOkHttpResponseBuilder.body(new ResponseBody() { - @Override - public MediaType contentType() { - if (parseResponse.getContentType() == null) { - return null; - } - return MediaType.parse(parseResponse.getContentType()); - } - - @Override - public long contentLength() { - return parseResponse.getTotalSize(); - } - - @Override - public BufferedSource source() { - // We need to use the proxy stream from interceptor to replace the origin network - // stream, so when the stream is read by Parse, the network stream is proxyed in the - // interceptor. - if (parseResponse.getContent() == null) { - return null; - } - return Okio.buffer(Okio.source(parseResponse.getContent())); - } - }); - - return newOkHttpResponseBuilder.build(); - } - }); - - okHttpClient = builder.build(); - } - - private static class ParseOkHttpRequestBody extends RequestBody { - - private ParseHttpBody parseBody; - - public ParseOkHttpRequestBody(ParseHttpBody parseBody) { - this.parseBody = parseBody; - } - - @Override - public long contentLength() throws IOException { - return parseBody.getContentLength(); - } - - @Override - public MediaType contentType() { - String contentType = parseBody.getContentType(); - return contentType == null ? null : MediaType.parse(parseBody.getContentType()); - } - - @Override - public void writeTo(BufferedSink bufferedSink) throws IOException { - parseBody.writeTo(bufferedSink.outputStream()); - } - - public ParseHttpBody getParseHttpBody() { - return parseBody; - } - } -} diff --git a/Parse/src/main/java/com/parse/ParsePlugins.java b/Parse/src/main/java/com/parse/ParsePlugins.java index 4ab874fcc..a8afa694f 100644 --- a/Parse/src/main/java/com/parse/ParsePlugins.java +++ b/Parse/src/main/java/com/parse/ParsePlugins.java @@ -10,230 +10,227 @@ import android.content.Context; import android.content.pm.PackageManager; -import android.net.SSLSessionCache; import android.os.Build; -import com.parse.http.ParseHttpRequest; -import com.parse.http.ParseHttpResponse; -import com.parse.http.ParseNetworkInterceptor; - import java.io.File; import java.io.IOException; -/** package */ class ParsePlugins { - - private static final String INSTALLATION_ID_LOCATION = "installationId"; - - private static final Object LOCK = new Object(); - private static ParsePlugins instance; - - // TODO(grantland): Move towards a Config/Builder parameter pattern to allow other configurations - // such as path (disabled for Android), etc. - /* package */ static void initialize(String applicationId, String clientKey) { - ParsePlugins.set(new ParsePlugins(applicationId, clientKey)); - } - - /* package for tests */ static void set(ParsePlugins plugins) { - synchronized (LOCK) { - if (instance != null) { - throw new IllegalStateException("ParsePlugins is already initialized"); - } - instance = plugins; - } - } - - /* package */ static ParsePlugins get() { - synchronized (LOCK) { - return instance; - } - } - - /* package */ static void reset() { - synchronized (LOCK) { - instance = null; - } - } - - /* package */ final Object lock = new Object(); - private final String applicationId; - private final String clientKey; - - private ParseHttpClient restClient; - private InstallationId installationId; - - /* package */ File parseDir; - /* package */ File cacheDir; - /* package */ File filesDir; - - private ParsePlugins(String applicationId, String clientKey) { - this.applicationId = applicationId; - this.clientKey = clientKey; - } - - /* package */ String applicationId() { - return applicationId; - } - - /* package */ String clientKey() { - return clientKey; - } - - /* package */ ParseHttpClient newHttpClient() { - int socketOperationTimeout = 10 * 1000; // 10 seconds - return ParseHttpClient.createClient( - socketOperationTimeout, - null); - } - - /* package */ ParseHttpClient restClient() { - synchronized (lock) { - if (restClient == null) { - restClient = newHttpClient(); - restClient.addInternalInterceptor(new ParseNetworkInterceptor() { - @Override - public ParseHttpResponse intercept(Chain chain) throws IOException { - ParseHttpRequest request = chain.getRequest(); - ParseHttpRequest.Builder builder = new ParseHttpRequest.Builder(request) - .addHeader(ParseRESTCommand.HEADER_APPLICATION_ID, applicationId) - .addHeader(ParseRESTCommand.HEADER_CLIENT_VERSION, Parse.externalVersionName()) - .addHeader( - ParseRESTCommand.HEADER_APP_BUILD_VERSION, - String.valueOf(ManifestInfo.getVersionCode())) - .addHeader( - ParseRESTCommand.HEADER_APP_DISPLAY_VERSION, - ManifestInfo.getVersionName()) - .addHeader(ParseRESTCommand.HEADER_OS_VERSION, Build.VERSION.RELEASE) - .addHeader(ParseRESTCommand.USER_AGENT, userAgent()); - - // Only add the installationId if not already set - if (request.getHeader(ParseRESTCommand.HEADER_INSTALLATION_ID) == null) { - // We can do this synchronously since the caller is already in a Task on the - // NETWORK_EXECUTOR - builder.addHeader(ParseRESTCommand.HEADER_INSTALLATION_ID, installationId().get()); - } - // client key can be null with self-hosted Parse Server - if (clientKey != null) { - builder.addHeader(ParseRESTCommand.HEADER_CLIENT_KEY, clientKey); +import okhttp3.Headers; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +class ParsePlugins { + + private static final String INSTALLATION_ID_LOCATION = "installationId"; + + private static final Object LOCK = new Object(); + private static ParsePlugins instance; + + // TODO(grantland): Move towards a Config/Builder parameter pattern to allow other configurations + // such as path (disabled for Android), etc. + static void initialize(Parse.Configuration configuration) { + ParsePlugins.set(new ParsePlugins(configuration)); + } + + static void set(ParsePlugins plugins) { + synchronized (LOCK) { + if (instance != null) { + throw new IllegalStateException("ParsePlugins is already initialized"); } - return chain.proceed(builder.build()); - } - }); - } - return restClient; + instance = plugins; + } } - } - // TODO(grantland): Pass through some system values. - /* package */ String userAgent() { - return "Parse Java SDK"; - } + static ParsePlugins get() { + synchronized (LOCK) { + return instance; + } + } - /* package */ InstallationId installationId() { - synchronized (lock) { - if (installationId == null) { - //noinspection deprecation - installationId = new InstallationId(new File(getParseDir(), INSTALLATION_ID_LOCATION)); - } - return installationId; + static void reset() { + synchronized (LOCK) { + instance = null; + } } - } - @Deprecated - /* package */ File getParseDir() { - throw new IllegalStateException("Stub"); - } + final Object lock = new Object(); + private final Parse.Configuration configuration; - /* package */ File getCacheDir() { - throw new IllegalStateException("Stub"); - } + private InstallationId installationId; - /* package */ File getFilesDir() { - throw new IllegalStateException("Stub"); - } + File parseDir; + File cacheDir; + File filesDir; - /* package */ static class Android extends ParsePlugins { - /* package */ static void initialize(Context context, String applicationId, String clientKey) { - ParsePlugins.set(new Android(context, applicationId, clientKey)); - } + ParseHttpClient restClient; + ParseHttpClient fileClient; - /* package */ static ParsePlugins.Android get() { - return (ParsePlugins.Android) ParsePlugins.get(); + private ParsePlugins(Parse.Configuration configuration) { + this.configuration = configuration; } - private final Context applicationContext; + String applicationId() { + return configuration.applicationId; + } - private Android(Context context, String applicationId, String clientKey) { - super(applicationId, clientKey); - applicationContext = context.getApplicationContext(); + String clientKey() { + return configuration.clientKey; } - /* package */ Context applicationContext() { - return applicationContext; + ParseHttpClient fileClient() { + synchronized (lock) { + if (fileClient == null) { + fileClient = ParseHttpClient.createClient(configuration.clientBuilder); + } + return fileClient; + } } - @Override - public ParseHttpClient newHttpClient() { - SSLSessionCache sslSessionCache = new SSLSessionCache(applicationContext); - int socketOperationTimeout = 10 * 1000; // 10 seconds - return ParseHttpClient.createClient( - socketOperationTimeout, - sslSessionCache); + ParseHttpClient restClient() { + synchronized (lock) { + if (restClient == null) { + OkHttpClient.Builder clientBuilder = configuration.clientBuilder; + if (clientBuilder == null) { + clientBuilder = new OkHttpClient.Builder(); + } + //add it as the first interceptor + clientBuilder.interceptors().add(0, new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Headers.Builder headersBuilder = request.headers().newBuilder() + .set(ParseRESTCommand.HEADER_APPLICATION_ID, configuration.applicationId) + .set(ParseRESTCommand.HEADER_CLIENT_VERSION, Parse.externalVersionName()) + .set(ParseRESTCommand.HEADER_APP_BUILD_VERSION, + String.valueOf(ManifestInfo.getVersionCode())) + .set(ParseRESTCommand.HEADER_APP_DISPLAY_VERSION, + ManifestInfo.getVersionName()); + if (request.header(ParseRESTCommand.HEADER_INSTALLATION_ID) == null) { + // We can do this synchronously since the caller is already on a background thread + headersBuilder.set(ParseRESTCommand.HEADER_INSTALLATION_ID, installationId().get()); + } + // client key can be null with self-hosted Parse Server + if (configuration.clientKey != null) { + headersBuilder.set(ParseRESTCommand.HEADER_CLIENT_KEY, configuration.clientKey); + } + request = request.newBuilder() + .headers(headersBuilder.build()) + .build(); + return chain.proceed(request); + } + }); + restClient = ParseHttpClient.createClient(configuration.clientBuilder); + } + return restClient; + } } - @Override - /* package */ String userAgent() { - String packageVersion = "unknown"; - try { - String packageName = applicationContext.getPackageName(); - int versionCode = applicationContext - .getPackageManager() - .getPackageInfo(packageName, 0) - .versionCode; - packageVersion = packageName + "/" + versionCode; - } catch (PackageManager.NameNotFoundException e) { - // Should never happen. - } - return "Parse Android SDK " + ParseObject.VERSION_NAME + " (" + packageVersion + - ") API Level " + Build.VERSION.SDK_INT; + // TODO(grantland): Pass through some system values. + String userAgent() { + return "Parse Java SDK"; } - @Override @SuppressWarnings("deprecation") - /* package */ File getParseDir() { - synchronized (lock) { - if (parseDir == null) { - parseDir = applicationContext.getDir("Parse", Context.MODE_PRIVATE); + InstallationId installationId() { + synchronized (lock) { + if (installationId == null) { + //noinspection deprecation + installationId = new InstallationId(new File(getParseDir(), INSTALLATION_ID_LOCATION)); + } + return installationId; } - return createFileDir(parseDir); - } } - @Override - /* package */ File getCacheDir() { - synchronized (lock) { - if (cacheDir == null) { - cacheDir = new File(applicationContext.getCacheDir(), "com.parse"); - } - return createFileDir(cacheDir); - } + @Deprecated + File getParseDir() { + throw new IllegalStateException("Stub"); + } + + File getCacheDir() { + throw new IllegalStateException("Stub"); + } + + File getFilesDir() { + throw new IllegalStateException("Stub"); } - @Override - /* package */ File getFilesDir() { - synchronized (lock) { - if (filesDir == null) { - filesDir = new File(applicationContext.getFilesDir(), "com.parse"); + static class Android extends ParsePlugins { + + static void initialize(Context context, Parse.Configuration configuration) { + ParsePlugins.set(new Android(context, configuration)); + } + + static ParsePlugins.Android get() { + return (ParsePlugins.Android) ParsePlugins.get(); + } + + private final Context applicationContext; + + private Android(Context context, Parse.Configuration configuration) { + super(configuration); + applicationContext = context.getApplicationContext(); + } + + Context applicationContext() { + return applicationContext; + } + + @Override + String userAgent() { + String packageVersion = "unknown"; + try { + String packageName = applicationContext.getPackageName(); + int versionCode = applicationContext + .getPackageManager() + .getPackageInfo(packageName, 0) + .versionCode; + packageVersion = packageName + "/" + versionCode; + } catch (PackageManager.NameNotFoundException e) { + // Should never happen. + } + return "Parse Android SDK " + ParseObject.VERSION_NAME + " (" + packageVersion + + ") API Level " + Build.VERSION.SDK_INT; + } + + @Override + @SuppressWarnings("deprecation") + File getParseDir() { + synchronized (lock) { + if (parseDir == null) { + parseDir = applicationContext.getDir("Parse", Context.MODE_PRIVATE); + } + return createFileDir(parseDir); + } + } + + @Override + File getCacheDir() { + synchronized (lock) { + if (cacheDir == null) { + cacheDir = new File(applicationContext.getCacheDir(), "com.parse"); + } + return createFileDir(cacheDir); + } + } + + @Override + File getFilesDir() { + synchronized (lock) { + if (filesDir == null) { + filesDir = new File(applicationContext.getFilesDir(), "com.parse"); + } + return createFileDir(filesDir); + } } - return createFileDir(filesDir); - } } - } - private static File createFileDir(File file) { - if (!file.exists()) { - if (!file.mkdirs()) { + private static File createFileDir(File file) { + if (!file.exists()) { + if (!file.mkdirs()) { + return file; + } + } return file; - } } - return file; - } } diff --git a/Parse/src/main/java/com/parse/http/ParseNetworkInterceptor.java b/Parse/src/main/java/com/parse/http/ParseNetworkInterceptor.java deleted file mode 100644 index 7d163079f..000000000 --- a/Parse/src/main/java/com/parse/http/ParseNetworkInterceptor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2015-present, Parse, LLC. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -package com.parse.http; - -import java.io.IOException; - -/** - * {@code ParseNetworkInterceptor} is used to observe requests going out and the corresponding - * responses coming back in. - */ -public interface ParseNetworkInterceptor { - - /** - * Intercepts a {@link ParseHttpRequest} with the help of - * {@link com.parse.http.ParseNetworkInterceptor.Chain} and returns the intercepted - * {@link ParseHttpResponse}. - * - * @param chain - * The helper chain we use to get the request, proceed the request and receive the - * response. - * @return The intercepted response. - * @throws IOException - */ - ParseHttpResponse intercept(Chain chain) throws IOException; - - /** - * {@code Chain} is used to chain the interceptors. It can get the request from the previous - * interceptor, proceed the request to the next interceptor and get the response from the next - * interceptor. In most of the cases, you don't need to implement this interface. - */ - interface Chain { - - /** - * Gets the {@link ParseHttpRequest} from this chain. - * - * @return The {@link ParseHttpRequest} of this chain. - */ - ParseHttpRequest getRequest(); - - /** - * Proceeds the intercepted {@link ParseHttpRequest} in this chain to next - * {@code ParseNetworkInterceptor} or network and gets the {@link ParseHttpResponse}. - * - * @param request - * The intercepted {@link ParseHttpRequest}. - * @return The {@link ParseHttpResponse} from next {@code ParseNetworkInterceptor} or network. - * @throws IOException - */ - ParseHttpResponse proceed(ParseHttpRequest request) throws IOException; - } -} diff --git a/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java b/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java index c743828f3..30874668c 100644 --- a/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java +++ b/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java @@ -29,19 +29,20 @@ import static org.mockito.Mockito.when; @SuppressWarnings("unchecked") -public class CachedCurrentUserControllerTest { +public class CachedCurrentUserControllerTest extends ResetPluginsParseTest { private static final String KEY_AUTH_DATA = "authData"; @Before - public void setUp() { + public void setUp() throws Exception { + super.setUp(); ParseObject.registerSubclass(ParseUser.class); } @After - public void tearDown() { + public void tearDown() throws Exception { + super.tearDown(); ParseObject.unregisterSubclass(ParseUser.class); - ParsePlugins.reset(); } //region testSetAsync diff --git a/Parse/src/test/java/com/parse/LocalIdManagerTest.java b/Parse/src/test/java/com/parse/LocalIdManagerTest.java index c91dc8f47..56b9e4f96 100644 --- a/Parse/src/test/java/com/parse/LocalIdManagerTest.java +++ b/Parse/src/test/java/com/parse/LocalIdManagerTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertNull; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class LocalIdManagerTest { @Rule diff --git a/Parse/src/test/java/com/parse/NetworkObjectControllerTest.java b/Parse/src/test/java/com/parse/NetworkObjectControllerTest.java index 1724ea811..a5df28f55 100644 --- a/Parse/src/test/java/com/parse/NetworkObjectControllerTest.java +++ b/Parse/src/test/java/com/parse/NetworkObjectControllerTest.java @@ -37,7 +37,7 @@ // For Uri.encode @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class NetworkObjectControllerTest { @Before diff --git a/Parse/src/test/java/com/parse/NetworkSessionControllerTest.java b/Parse/src/test/java/com/parse/NetworkSessionControllerTest.java index e82e6e2e2..b4f21413c 100644 --- a/Parse/src/test/java/com/parse/NetworkSessionControllerTest.java +++ b/Parse/src/test/java/com/parse/NetworkSessionControllerTest.java @@ -22,12 +22,11 @@ import java.util.Map; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; // For Uri.encode @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class NetworkSessionControllerTest { @Before diff --git a/Parse/src/test/java/com/parse/NetworkUserControllerTest.java b/Parse/src/test/java/com/parse/NetworkUserControllerTest.java index 77e97b5bb..dc56231b9 100644 --- a/Parse/src/test/java/com/parse/NetworkUserControllerTest.java +++ b/Parse/src/test/java/com/parse/NetworkUserControllerTest.java @@ -28,7 +28,7 @@ // For Uri.encode @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class NetworkUserControllerTest { @Before diff --git a/Parse/src/test/java/com/parse/ParseAnalyticsControllerTest.java b/Parse/src/test/java/com/parse/ParseAnalyticsControllerTest.java index 77ec0ed47..a5c0ee19e 100644 --- a/Parse/src/test/java/com/parse/ParseAnalyticsControllerTest.java +++ b/Parse/src/test/java/com/parse/ParseAnalyticsControllerTest.java @@ -36,7 +36,7 @@ // For android.net.Uri @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseAnalyticsControllerTest { @Before diff --git a/Parse/src/test/java/com/parse/ParseAnalyticsTest.java b/Parse/src/test/java/com/parse/ParseAnalyticsTest.java index 43d350edb..8e9106c9e 100644 --- a/Parse/src/test/java/com/parse/ParseAnalyticsTest.java +++ b/Parse/src/test/java/com/parse/ParseAnalyticsTest.java @@ -43,7 +43,7 @@ // For android.os.BaseBundle @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseAnalyticsTest { ParseAnalyticsController controller; diff --git a/Parse/src/test/java/com/parse/ParseClientConfigurationTest.java b/Parse/src/test/java/com/parse/ParseClientConfigurationTest.java index 1f6e37857..20626788a 100644 --- a/Parse/src/test/java/com/parse/ParseClientConfigurationTest.java +++ b/Parse/src/test/java/com/parse/ParseClientConfigurationTest.java @@ -12,8 +12,6 @@ import android.content.pm.PackageManager; import android.os.Bundle; -import com.parse.http.ParseNetworkInterceptor; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -22,15 +20,11 @@ import org.robolectric.shadows.ShadowPackageManager; import java.net.URL; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -77,56 +71,6 @@ public void testBuilderServerMissingSlashURL() { assertEquals(configuration.server, "http://myserver.com/missingslash/"); } - @Test - public void testNetworkInterceptors() { - ParseNetworkInterceptor interceptorA = mock(ParseNetworkInterceptor.class); - ParseNetworkInterceptor interceptorB = mock(ParseNetworkInterceptor.class); - - Parse.Configuration.Builder builder = new Parse.Configuration.Builder(null); - - builder.addNetworkInterceptor(interceptorA); - Parse.Configuration configurationA = builder.build(); - builder.addNetworkInterceptor(interceptorB); - Parse.Configuration configurationB = builder.build(); - - assertFalse(configurationA.interceptors.contains(interceptorB)); - assertTrue(configurationB.interceptors.contains(interceptorB)); - - try { - configurationA.interceptors.add(interceptorB); - fail("Interceptors shouldn't be mutable."); - } catch (UnsupportedOperationException ex) { - // Expected - } - } - - @Test - public void testSetNetworkInterceptors() { - final ParseNetworkInterceptor interceptorA = mock(ParseNetworkInterceptor.class); - final ParseNetworkInterceptor interceptorB = mock(ParseNetworkInterceptor.class); - - Collection collectionA = new ArrayList() {{ - add(interceptorA); - add(interceptorB); - }}; - - Collection collectionB = new ArrayList() {{ - add(interceptorB); - add(interceptorA); - }}; - - Parse.Configuration.Builder builder = new Parse.Configuration.Builder(null); - - builder.setNetworkInterceptors(collectionA); - Parse.Configuration configurationA = builder.build(); - - builder.setNetworkInterceptors(collectionB); - Parse.Configuration configurationB = builder.build(); - - assertTrue(collectionsEqual(configurationA.interceptors, collectionA)); - assertTrue(collectionsEqual(configurationB.interceptors, collectionB)); - } - @Test public void testConfigureFromManifest() throws Exception { Bundle metaData = setupMockMetaData(); diff --git a/Parse/src/test/java/com/parse/ParseCloudTest.java b/Parse/src/test/java/com/parse/ParseCloudTest.java index 4a8d052eb..f5f35ec14 100644 --- a/Parse/src/test/java/com/parse/ParseCloudTest.java +++ b/Parse/src/test/java/com/parse/ParseCloudTest.java @@ -8,7 +8,6 @@ */ package com.parse; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,19 +39,15 @@ // For android.os.Looper @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) -public class ParseCloudTest { +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) +public class ParseCloudTest extends ResetPluginsParseTest { @Before - public void setUp() { + public void setUp() throws Exception { + super.setUp(); ParseTestUtils.setTestParseUser(); } - @After - public void tearDown() { - ParseCorePlugins.getInstance().reset(); - } - //region testGetCloudCodeController @Test diff --git a/Parse/src/test/java/com/parse/ParseCoderTest.java b/Parse/src/test/java/com/parse/ParseCoderTest.java index 13eb9f530..59ba76e65 100644 --- a/Parse/src/test/java/com/parse/ParseCoderTest.java +++ b/Parse/src/test/java/com/parse/ParseCoderTest.java @@ -18,7 +18,7 @@ // For android.util.Base64 @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseCoderTest { @Test diff --git a/Parse/src/test/java/com/parse/ParseConfigTest.java b/Parse/src/test/java/com/parse/ParseConfigTest.java index d664b3a10..48e29790b 100644 --- a/Parse/src/test/java/com/parse/ParseConfigTest.java +++ b/Parse/src/test/java/com/parse/ParseConfigTest.java @@ -47,7 +47,7 @@ // For android.os.Looper @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseConfigTest { @Before diff --git a/Parse/src/test/java/com/parse/ParseCorePluginsTest.java b/Parse/src/test/java/com/parse/ParseCorePluginsTest.java index 0b8a43dea..f23b6b10c 100644 --- a/Parse/src/test/java/com/parse/ParseCorePluginsTest.java +++ b/Parse/src/test/java/com/parse/ParseCorePluginsTest.java @@ -8,7 +8,6 @@ */ package com.parse; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -23,20 +22,17 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; -// For org.apache.http @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) -public class ParseCorePluginsTest { +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) +public class ParseCorePluginsTest extends ResetPluginsParseTest { @Before - public void setUp() { - ParsePlugins.initialize("1234", "1234"); - } - - @After - public void tearDown() { - ParseCorePlugins.getInstance().reset(); - ParsePlugins.reset(); + public void setUp() throws Exception { + super.setUp(); + Parse.Configuration configuration = new Parse.Configuration.Builder(null) + .applicationId("1234") + .build(); + ParsePlugins.initialize(configuration); } @Test diff --git a/Parse/src/test/java/com/parse/ParseDecoderTest.java b/Parse/src/test/java/com/parse/ParseDecoderTest.java index 0c8bf4013..f7f64abb3 100644 --- a/Parse/src/test/java/com/parse/ParseDecoderTest.java +++ b/Parse/src/test/java/com/parse/ParseDecoderTest.java @@ -31,8 +31,8 @@ // For android.util.Base64 @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) -public class ParseDecoderTest { +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) +public class ParseDecoderTest extends ResetPluginsParseTest { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/Parse/src/test/java/com/parse/ParseDecompressInterceptorTest.java b/Parse/src/test/java/com/parse/ParseDecompressInterceptorTest.java deleted file mode 100644 index dc91e58a9..000000000 --- a/Parse/src/test/java/com/parse/ParseDecompressInterceptorTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2015-present, Parse, LLC. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.parse; - -import com.parse.http.ParseHttpRequest; -import com.parse.http.ParseHttpResponse; -import com.parse.http.ParseNetworkInterceptor; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.GZIPOutputStream; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) -public class ParseDecompressInterceptorTest { - - @Test - public void testDecompressInterceptorWithNotGZIPResponse() throws Exception { - ParseDecompressInterceptor interceptor = new ParseDecompressInterceptor(); - - final String responseContent = "content"; - ParseHttpResponse interceptedResponse = - interceptor.intercept(new ParseNetworkInterceptor.Chain() { - @Override - public ParseHttpRequest getRequest() { - // Generate test request - return new ParseHttpRequest.Builder() - .setUrl("www.parse.com") - .setMethod(ParseHttpRequest.Method.GET) - .build(); - } - - @Override - public ParseHttpResponse proceed(ParseHttpRequest request) throws IOException { - // Generate test response - return new ParseHttpResponse.Builder() - .setStatusCode(200) - .setTotalSize(responseContent.length()) - .setReasonPhrase("Success") - .setContentType("text/plain") - .setContent(new ByteArrayInputStream(responseContent.getBytes())) - .build(); - } - }); - - // Verify response is correct - assertEquals(200, interceptedResponse.getStatusCode()); - assertEquals(responseContent.length(), interceptedResponse.getTotalSize()); - assertEquals("Success", interceptedResponse.getReasonPhrase()); - assertEquals("text/plain", interceptedResponse.getContentType()); - byte[] content = ParseIOUtils.toByteArray(interceptedResponse.getContent()); - assertArrayEquals(responseContent.getBytes(), content); - } - - @Test - public void testDecompressInterceptorWithGZIPResponse() throws Exception { - ParseDecompressInterceptor interceptor = new ParseDecompressInterceptor(); - - final String responseContent = "content"; - ParseHttpResponse interceptedResponse = - interceptor.intercept(new ParseNetworkInterceptor.Chain() { - @Override - public ParseHttpRequest getRequest() { - // Generate test request - return new ParseHttpRequest.Builder() - .setUrl("www.parse.com") - .setMethod(ParseHttpRequest.Method.GET) - .build(); - } - - @Override - public ParseHttpResponse proceed(ParseHttpRequest request) throws IOException { - // Make gzip response content - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - GZIPOutputStream gzipOut = new GZIPOutputStream(byteOut); - gzipOut.write(responseContent.getBytes()); - gzipOut.close(); - // Make gzip encoding headers - Map headers = new HashMap<>(); - headers.put("Content-Encoding", "gzip"); - // Generate test response - return new ParseHttpResponse.Builder() - .setStatusCode(200) - .setTotalSize(byteOut.toByteArray().length) - .setReasonPhrase("Success") - .setContentType("text/plain") - .setContent(new ByteArrayInputStream(byteOut.toByteArray())) - .setHeaders(headers) - .build(); - } - }); - - // Verify response is correct - assertEquals(200, interceptedResponse.getStatusCode()); - assertEquals(-1, interceptedResponse.getTotalSize()); - assertEquals("Success", interceptedResponse.getReasonPhrase()); - assertEquals("text/plain", interceptedResponse.getContentType()); - assertNull(interceptedResponse.getHeader("Content-Encoding")); - byte[] content = ParseIOUtils.toByteArray(interceptedResponse.getContent()); - assertArrayEquals(responseContent.getBytes(), content); - } -} diff --git a/Parse/src/test/java/com/parse/ParseEncoderTest.java b/Parse/src/test/java/com/parse/ParseEncoderTest.java index 50cf0e38b..23cdd4f09 100644 --- a/Parse/src/test/java/com/parse/ParseEncoderTest.java +++ b/Parse/src/test/java/com/parse/ParseEncoderTest.java @@ -25,12 +25,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; // For android.util.Base64 @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseEncoderTest { ParseEncoderTestClass testClassObject = null; diff --git a/Parse/src/test/java/com/parse/ParseFileControllerTest.java b/Parse/src/test/java/com/parse/ParseFileControllerTest.java index d0a7ba319..63993a1b2 100644 --- a/Parse/src/test/java/com/parse/ParseFileControllerTest.java +++ b/Parse/src/test/java/com/parse/ParseFileControllerTest.java @@ -43,7 +43,7 @@ // For org.json @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseFileControllerTest { @Before @@ -269,23 +269,23 @@ public void testFetchAsyncRequest() { @Test public void testFetchAsyncAlreadyCancelled() throws Exception{ - ParseHttpClient awsClient = mock(ParseHttpClient.class); - ParseFileController controller = new ParseFileController(null, null).awsClient(awsClient); + ParseHttpClient fileClient = mock(ParseHttpClient.class); + ParseFileController controller = new ParseFileController(null, null).fileClient(fileClient); ParseFile.State state = new ParseFile.State.Builder().build(); Task cancellationToken = Task.cancelled(); Task task = controller.fetchAsync(state, null, null, cancellationToken); task.waitForCompletion(); - verify(awsClient, times(0)).execute(any(ParseHttpRequest.class)); + verify(fileClient, times(0)).execute(any(ParseHttpRequest.class)); assertTrue(task.isCancelled()); } @Test public void testFetchAsyncCached() throws Exception { - ParseHttpClient awsClient = mock(ParseHttpClient.class); + ParseHttpClient fileClient = mock(ParseHttpClient.class); File root = temporaryFolder.getRoot(); - ParseFileController controller = new ParseFileController(null, root).awsClient(awsClient); + ParseFileController controller = new ParseFileController(null, root).fileClient(fileClient); File file = new File(root, "cached_file_name"); ParseFileUtils.writeStringToFile(file, "hello", "UTF-8"); @@ -296,7 +296,7 @@ public void testFetchAsyncCached() throws Exception { Task task = controller.fetchAsync(state, null, null, null); File result = ParseTaskUtils.wait(task); - verify(awsClient, times(0)).execute(any(ParseHttpRequest.class)); + verify(fileClient, times(0)).execute(any(ParseHttpRequest.class)); assertEquals(file, result); assertEquals("hello", ParseFileUtils.readFileToString(result, "UTF-8")); } @@ -310,12 +310,12 @@ public void testFetchAsyncSuccess() throws Exception { .setContent(new ByteArrayInputStream(data)) .build(); - ParseHttpClient awsClient = mock(ParseHttpClient.class); - when(awsClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse); + ParseHttpClient fileClient = mock(ParseHttpClient.class); + when(fileClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse); // Make sure cache dir does not exist File root = new File(temporaryFolder.getRoot(), "cache"); assertFalse(root.exists()); - ParseFileController controller = new ParseFileController(null, root).awsClient(awsClient); + ParseFileController controller = new ParseFileController(null, root).fileClient(fileClient); ParseFile.State state = new ParseFile.State.Builder() .name("file_name") @@ -324,7 +324,7 @@ public void testFetchAsyncSuccess() throws Exception { Task task = controller.fetchAsync(state, null, null, null); File result = ParseTaskUtils.wait(task); - verify(awsClient, times(1)).execute(any(ParseHttpRequest.class)); + verify(fileClient, times(1)).execute(any(ParseHttpRequest.class)); assertTrue(result.exists()); assertEquals("hello", ParseFileUtils.readFileToString(result, "UTF-8")); assertFalse(controller.getTempFile(state).exists()); @@ -335,11 +335,11 @@ public void testFetchAsyncFailure() throws Exception { // TODO(grantland): Remove once we no longer rely on retry logic. ParseRequest.setDefaultInitialRetryDelay(1L); - ParseHttpClient awsClient = mock(ParseHttpClient.class); - when(awsClient.execute(any(ParseHttpRequest.class))).thenThrow(new IOException()); + ParseHttpClient fileClient = mock(ParseHttpClient.class); + when(fileClient.execute(any(ParseHttpRequest.class))).thenThrow(new IOException()); File root = temporaryFolder.getRoot(); - ParseFileController controller = new ParseFileController(null, root).awsClient(awsClient); + ParseFileController controller = new ParseFileController(null, root).fileClient(fileClient); // We need to set url to make getTempFile() work and check it ParseFile.State state = new ParseFile.State.Builder() @@ -349,7 +349,7 @@ public void testFetchAsyncFailure() throws Exception { task.waitForCompletion(); // TODO(grantland): Abstract out command runner so we don't have to account for retries. - verify(awsClient, times(5)).execute(any(ParseHttpRequest.class)); + verify(fileClient, times(5)).execute(any(ParseHttpRequest.class)); assertTrue(task.isFaulted()); Exception error = task.getError(); assertThat(error, instanceOf(ParseException.class)); diff --git a/Parse/src/test/java/com/parse/ParseAWSRequestTest.java b/Parse/src/test/java/com/parse/ParseFileRequestTest.java similarity index 88% rename from Parse/src/test/java/com/parse/ParseAWSRequestTest.java rename to Parse/src/test/java/com/parse/ParseFileRequestTest.java index 21e7c5ef9..3896efc78 100644 --- a/Parse/src/test/java/com/parse/ParseAWSRequestTest.java +++ b/Parse/src/test/java/com/parse/ParseFileRequestTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ParseAWSRequestTest extends TestCase { +public class ParseFileRequestTest extends TestCase { @Override protected void tearDown() throws Exception { @@ -45,8 +45,8 @@ public void test4XXThrowsException() throws Exception { ParseHttpClient mockHttpClient = mock(ParseHttpClient.class); when(mockHttpClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse); - ParseAWSRequest request = - new ParseAWSRequest(ParseHttpRequest.Method.GET, "http://parse.com", null); + ParseFileRequest request = + new ParseFileRequest(ParseHttpRequest.Method.GET, "http://parse.com", null); Task task = request.executeAsync(mockHttpClient); task.waitForCompletion(); @@ -54,6 +54,6 @@ public void test4XXThrowsException() throws Exception { assertTrue(task.getError() instanceof ParseException); ParseException error = (ParseException) task.getError(); assertEquals(error.getCode(), ParseException.CONNECTION_FAILED); - assertTrue(error.getMessage().contains("Download from S3")); + assertTrue(error.getMessage().contains("Download from file server")); } } diff --git a/Parse/src/test/java/com/parse/ParseFileStateTest.java b/Parse/src/test/java/com/parse/ParseFileStateTest.java index 5a7613fe0..8bf463bf1 100644 --- a/Parse/src/test/java/com/parse/ParseFileStateTest.java +++ b/Parse/src/test/java/com/parse/ParseFileStateTest.java @@ -24,7 +24,7 @@ // For android.webkit.MimeTypeMap @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseFileStateTest { @Before diff --git a/Parse/src/test/java/com/parse/ParseFileUtilsTest.java b/Parse/src/test/java/com/parse/ParseFileUtilsTest.java index fd9d7abbb..03d94ce06 100644 --- a/Parse/src/test/java/com/parse/ParseFileUtilsTest.java +++ b/Parse/src/test/java/com/parse/ParseFileUtilsTest.java @@ -27,7 +27,7 @@ import static org.junit.Assert.assertNotNull; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseFileUtilsTest { private static final String TEST_STRING = "this is a test string"; diff --git a/Parse/src/test/java/com/parse/ParseHttpClientTest.java b/Parse/src/test/java/com/parse/ParseHttpClientTest.java index b372eabc5..f17afda31 100644 --- a/Parse/src/test/java/com/parse/ParseHttpClientTest.java +++ b/Parse/src/test/java/com/parse/ParseHttpClientTest.java @@ -24,6 +24,7 @@ import java.util.zip.GZIPOutputStream; import okhttp3.Headers; +import okhttp3.OkHttpClient; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -36,7 +37,7 @@ import static org.junit.Assert.assertTrue; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseHttpClientTest { // We can not use ParameterizedRobolectricTestRunner right now since Robolectric use @@ -46,12 +47,12 @@ public class ParseHttpClientTest { @Test public void testParseOkHttpClientExecuteWithSuccessResponse() throws Exception { doSingleParseHttpClientExecuteWithResponse( - 200, "OK", "Success", new ParseOkHttpClient(10000, null)); } + 200, "OK", "Success", ParseHttpClient.createClient(new OkHttpClient.Builder())); } @Test public void testParseOkHttpClientExecuteWithErrorResponse() throws Exception { doSingleParseHttpClientExecuteWithResponse( - 404, "NOT FOUND", "Error", new ParseOkHttpClient(10000, null)); } + 404, "NOT FOUND", "Error", ParseHttpClient.createClient(new OkHttpClient.Builder())); } // TODO(mengyan): Add testParseURLConnectionHttpClientExecuteWithGzipResponse, right now we can // not do that since in unit test env, URLConnection does not use OKHttp internally, so there is @@ -60,7 +61,7 @@ public void testParseOkHttpClientExecuteWithErrorResponse() throws Exception { @Test public void testParseOkHttpClientExecuteWithGzipResponse() throws Exception { doSingleParseHttpClientExecuteWithGzipResponse( - 200, "OK", "Success", new ParseOkHttpClient(10000, null)); + 200, "OK", "Success", ParseHttpClient.createClient(new OkHttpClient.Builder())); } private void doSingleParseHttpClientExecuteWithResponse(int responseCode, String responseStatus, diff --git a/Parse/src/test/java/com/parse/ParseInstallationTest.java b/Parse/src/test/java/com/parse/ParseInstallationTest.java index ecb3e5748..a369f4924 100644 --- a/Parse/src/test/java/com/parse/ParseInstallationTest.java +++ b/Parse/src/test/java/com/parse/ParseInstallationTest.java @@ -42,8 +42,8 @@ import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) -public class ParseInstallationTest { +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) +public class ParseInstallationTest extends ResetPluginsParseTest { private static final String KEY_INSTALLATION_ID = "installationId"; private static final String KEY_DEVICE_TYPE = "deviceType"; private static final String KEY_APP_NAME = "appName"; @@ -55,17 +55,17 @@ public class ParseInstallationTest { private Locale defaultLocale; @Before - public void setUp() { + public void setUp() throws Exception { + super.setUp(); ParseObject.registerSubclass(ParseInstallation.class); defaultLocale = Locale.getDefault(); } @After - public void tearDown() { + public void tearDown() throws Exception { + super.tearDown(); ParseObject.unregisterSubclass(ParseInstallation.class); - ParseCorePlugins.getInstance().reset(); - ParsePlugins.reset(); Locale.setDefault(defaultLocale); } diff --git a/Parse/src/test/java/com/parse/ParseOkHttpClientTest.java b/Parse/src/test/java/com/parse/ParseOkHttpClientTest.java index 1a500c2c5..286862d01 100644 --- a/Parse/src/test/java/com/parse/ParseOkHttpClientTest.java +++ b/Parse/src/test/java/com/parse/ParseOkHttpClientTest.java @@ -10,25 +10,20 @@ import com.parse.http.ParseHttpRequest; import com.parse.http.ParseHttpResponse; -import com.parse.http.ParseNetworkInterceptor; -import org.json.JSONException; -import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; import java.util.zip.GZIPOutputStream; import okhttp3.MediaType; +import okhttp3.OkHttpClient; import okhttp3.Protocol; import okhttp3.Request; import okhttp3.RequestBody; @@ -36,17 +31,15 @@ import okhttp3.ResponseBody; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; import okio.Buffer; import okio.BufferedSource; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseOkHttpClientTest { private MockWebServer server = new MockWebServer(); @@ -55,7 +48,7 @@ public class ParseOkHttpClientTest { @Test public void testGetOkHttpRequestType() throws IOException { - ParseOkHttpClient parseClient = new ParseOkHttpClient(10000, null); + ParseHttpClient parseClient = ParseHttpClient.createClient(new OkHttpClient.Builder()); ParseHttpRequest.Builder builder = new ParseHttpRequest.Builder(); builder.setUrl("http://www.parse.com"); @@ -111,7 +104,7 @@ public void testGetOkHttpRequest() throws IOException { .setHeaders(headers) .build(); - ParseOkHttpClient parseClient = new ParseOkHttpClient(10000, null); + ParseHttpClient parseClient = ParseHttpClient.createClient(new OkHttpClient.Builder()); Request okHttpRequest = parseClient.getRequest(parseRequest); // Verify method @@ -144,7 +137,7 @@ public void testGetOkHttpRequestWithEmptyContentType() throws Exception { .setBody(new ParseByteArrayHttpBody(content, null)) .build(); - ParseOkHttpClient parseClient = new ParseOkHttpClient(10000, null); + ParseHttpClient parseClient = ParseHttpClient.createClient(new OkHttpClient.Builder()); Request okHttpRequest = parseClient.getRequest(parseRequest); // Verify Content-Type @@ -187,7 +180,7 @@ public BufferedSource source() { }) .build(); - ParseOkHttpClient parseClient = new ParseOkHttpClient(10000, null); + ParseHttpClient parseClient = ParseHttpClient.createClient(new OkHttpClient.Builder()); ParseHttpResponse parseResponse = parseClient.getResponse(okHttpResponse); // Verify status code @@ -205,17 +198,7 @@ public BufferedSource source() { //region testOkHttpClientWithInterceptor @Test - public void testParseOkHttpClientExecuteWithInternalInterceptor() throws Exception { - testParseOkHttpClientExecuteWithInterceptor(true); - } - - @Test - public void testParseOkHttpClientExecuteWithExternalInterceptor() throws Exception { - testParseOkHttpClientExecuteWithInterceptor(false); - } - - @Test - public void testParseOkHttpClientExecuteWithExternalInterceptorAndGZIPResponse() throws Exception { + public void testParseOkHttpClientExecuteWithGZIPResponse() throws Exception { // Make mock response Buffer buffer = new Buffer(); final ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); @@ -232,25 +215,7 @@ public void testParseOkHttpClientExecuteWithExternalInterceptorAndGZIPResponse() server.enqueue(mockResponse); server.start(); - ParseHttpClient client = new ParseOkHttpClient(10000, null); - - final Semaphore done = new Semaphore(0); - // Add plain interceptor to disable decompress response stream - client.addExternalInterceptor(new ParseNetworkInterceptor() { - @Override - public ParseHttpResponse intercept(Chain chain) throws IOException { - done.release(); - ParseHttpResponse parseResponse = chain.proceed(chain.getRequest()); - // Make sure the response we get from the interceptor is the raw gzip stream - byte[] content = ParseIOUtils.toByteArray(parseResponse.getContent()); - assertArrayEquals(byteOut.toByteArray(), content); - - // We need to set a new stream since we have read it - return new ParseHttpResponse.Builder() - .setContent(new ByteArrayInputStream(byteOut.toByteArray())) - .build(); - } - }); + ParseHttpClient client = ParseHttpClient.createClient(new OkHttpClient.Builder()); // We do not need to add Accept-Encoding header manually, httpClient library should do that. String requestUrl = server.url("/").toString(); @@ -265,172 +230,9 @@ public ParseHttpResponse intercept(Chain chain) throws IOException { // Make sure the response we get is ungziped by OkHttp library byte[] content = ParseIOUtils.toByteArray(parseResponse.getContent()); assertArrayEquals("content".getBytes(), content); - // Make sure interceptor is called - assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); server.shutdown(); } - // This test is used to test okHttp interceptors. The difference between external and - // internal interceptor is the external interceptor is added to OkHttpClient level, an internal - // interceptor is added to ParseHttpClient level. - // In the interceptor, we change request and response to see whether our server and - // ParseHttpClient can receive the correct value. - private void testParseOkHttpClientExecuteWithInterceptor( - boolean isInternalInterceptorTest) throws Exception { - // Start mock server - server.enqueue(generateServerResponse()); - server.start(); - - ParseHttpClient client = new ParseOkHttpClient(10000, null); - - // Make ParseHttpRequest - ParseHttpRequest parseRequest = generateClientRequest(); - - final Semaphore done = new Semaphore(0); - ParseNetworkInterceptor interceptor = new ParseNetworkInterceptor() { - @Override - public ParseHttpResponse intercept(Chain chain) throws IOException { - done.release(); - - ParseHttpRequest request = chain.getRequest(); - - // Verify original request - verifyClientRequest(request); - - // Change request - ParseHttpRequest requestAgain = generateInterceptorRequest(); - - // Proceed - ParseHttpResponse parseResponse = chain.proceed(requestAgain); - - // Verify original response - verifyServerResponse(parseResponse); - - // Change response - return generateInterceptorResponse(); - } - }; - - // Add interceptor - if (isInternalInterceptorTest) { - client.addInternalInterceptor(interceptor); - } else { - client.addExternalInterceptor(interceptor); - } - - // Execute request - ParseHttpResponse parseResponse = client.execute(parseRequest); - - // Make sure interceptor is called - assertTrue(done.tryAcquire(5, TimeUnit.SECONDS)); - - RecordedRequest recordedRequest = server.takeRequest(); - // Verify request changed by interceptor - verifyInterceptorRequest(recordedRequest); - - // Verify response changed by interceptor - verifyInterceptorResponse(parseResponse); - } - - // Generate a mocked Server response - private MockResponse generateServerResponse() { - MockResponse mockServerResponse = new MockResponse() - .setStatus("HTTP/1.1 " + 200 + " " + "OK") - .setBody("Success") - .setHeader("responseKey", "responseValue"); - return mockServerResponse; - } - - // Verify the mocked server response, if you change the data in generateServerResponse, make - // sure you also change the condition in this method otherwise tests will fail - private void verifyServerResponse(ParseHttpResponse parseResponse) throws IOException { - assertEquals(200, parseResponse.getStatusCode()); - assertEquals("OK", parseResponse.getReasonPhrase()); - assertEquals("responseValue", parseResponse.getHeader("responseKey")); - byte[] content = ParseIOUtils.toByteArray(parseResponse.getContent()); - assertArrayEquals("Success".getBytes(), content); - assertEquals(7, content.length); - } - - // Generate a ParseHttpRequest sent to server - private ParseHttpRequest generateClientRequest() throws Exception { - Map headers = new HashMap<>(); - headers.put("requestkey", "requestValue"); - JSONObject json = new JSONObject(); - json.put("key", "value"); - ParseHttpRequest parseRequest = new ParseHttpRequest.Builder() - .setUrl(server.url("/").toString()) - .setMethod(ParseHttpRequest.Method.POST) - .setBody(new ParseByteArrayHttpBody(json.toString().getBytes(), "application/json")) - .setHeaders(headers) - .build(); - return parseRequest; - } - - // Verify the request from client, if you change the data in generateClientRequest, make - // sure you also change the condition in this method otherwise tests will fail - private void verifyClientRequest(ParseHttpRequest parseRequest) throws IOException { - assertEquals(server.url("/").toString(), parseRequest.getUrl()); - assertEquals(ParseHttpRequest.Method.POST, parseRequest.getMethod()); - assertEquals("requestValue", parseRequest.getHeader("requestkey")); - assertEquals("application/json", parseRequest.getBody().getContentType()); - JSONObject json = new JSONObject(); - try { - json.put("key", "value"); - } catch (JSONException e) { - // do no - } - assertArrayEquals( - json.toString().getBytes(), - ParseIOUtils.toByteArray(parseRequest.getBody().getContent())); - } - - // Generate a ParseHttpRequest sent from interceptor - private ParseHttpRequest generateInterceptorRequest() { - ParseHttpRequest requestAgain = - new ParseHttpRequest.Builder() - .addHeader("requestKeyAgain", "requestValueAgain") - .setUrl(server.url("/test").toString()) - .setMethod(ParseHttpRequest.Method.GET) - .build(); - return requestAgain; - } - - // Verify the request from interceptor, if you change the data in generateInterceptorRequest, make - // sure you also change the condition in this method otherwise tests will fail - private void verifyInterceptorRequest(RecordedRequest recordedRequest) throws IOException { - assertEquals("/test", recordedRequest.getPath()); - assertEquals(ParseHttpRequest.Method.GET.toString(), recordedRequest.getMethod()); - assertEquals("requestValueAgain", recordedRequest.getHeader("requestKeyAgain")); - } - - // Generate a ParseHttpResponse returned from interceptor - private ParseHttpResponse generateInterceptorResponse() { - final String newResponseHeaderKey = "responseKey"; - final String newResponseHeaderValue = "responseValue"; - final Map newResponseHeaders = new HashMap<>(); - newResponseHeaders.put(newResponseHeaderKey, newResponseHeaderValue); - return new ParseHttpResponse.Builder() - .setStatusCode(201) - .setReasonPhrase("Fine") - .setContent(new ByteArrayInputStream("content".getBytes())) - .setTotalSize("content".length()) - .setHeaders(newResponseHeaders) - .build(); - } - - // Verify the response from interceptor, if you change the data in generateInterceptorResponse, - // make sure you also change the condition in this method otherwise tests will fail - private void verifyInterceptorResponse(ParseHttpResponse parseResponse) - throws IOException { - assertEquals(201, parseResponse.getStatusCode()); - assertEquals("Fine", parseResponse.getReasonPhrase()); - assertEquals("responseValue", parseResponse.getHeader("responseKey")); - byte[] content = ParseIOUtils.toByteArray(parseResponse.getContent()); - assertArrayEquals("content".getBytes(), content); - assertEquals("content".length(), content.length); - } - //endregion } diff --git a/Parse/src/test/java/com/parse/ParsePushControllerTest.java b/Parse/src/test/java/com/parse/ParsePushControllerTest.java index 6c9e8e437..6efac9730 100644 --- a/Parse/src/test/java/com/parse/ParsePushControllerTest.java +++ b/Parse/src/test/java/com/parse/ParsePushControllerTest.java @@ -45,7 +45,7 @@ // For SSLSessionCache @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParsePushControllerTest { @Before diff --git a/Parse/src/test/java/com/parse/ParsePushTest.java b/Parse/src/test/java/com/parse/ParsePushTest.java index e12474aeb..936389268 100644 --- a/Parse/src/test/java/com/parse/ParsePushTest.java +++ b/Parse/src/test/java/com/parse/ParsePushTest.java @@ -39,7 +39,7 @@ import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParsePushTest { @Before diff --git a/Parse/src/test/java/com/parse/ParseQueryStateTest.java b/Parse/src/test/java/com/parse/ParseQueryStateTest.java index f1980e952..17efa901d 100644 --- a/Parse/src/test/java/com/parse/ParseQueryStateTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryStateTest.java @@ -29,8 +29,8 @@ import static org.junit.Assert.assertTrue; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) -public class ParseQueryStateTest { +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) +public class ParseQueryStateTest extends ResetPluginsParseTest { @Test public void testDefaults() { diff --git a/Parse/src/test/java/com/parse/ParseRESTCommandTest.java b/Parse/src/test/java/com/parse/ParseRESTCommandTest.java index 4dabbd069..333ae0a1d 100644 --- a/Parse/src/test/java/com/parse/ParseRESTCommandTest.java +++ b/Parse/src/test/java/com/parse/ParseRESTCommandTest.java @@ -26,7 +26,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; import java.net.URL; import bolts.Task; @@ -47,7 +46,7 @@ // For org.json @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public class ParseRESTCommandTest { diff --git a/Parse/src/test/java/com/parse/ParseRequestTest.java b/Parse/src/test/java/com/parse/ParseRequestTest.java index 09e72a3dc..759cff6b3 100644 --- a/Parse/src/test/java/com/parse/ParseRequestTest.java +++ b/Parse/src/test/java/com/parse/ParseRequestTest.java @@ -77,7 +77,7 @@ public void testRetryLogic() throws Exception { verify(mockHttpClient, times(5)).execute(any(ParseHttpRequest.class)); } - // TODO(grantland): Move to ParseAWSRequestTest or ParseCountingByteArrayHttpBodyTest + // TODO(grantland): Move to ParseFileRequestTest or ParseCountingByteArrayHttpBodyTest @Test public void testDownloadProgress() throws Exception { ParseHttpResponse mockResponse = new ParseHttpResponse.Builder() @@ -90,8 +90,8 @@ public void testDownloadProgress() throws Exception { when(mockHttpClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse); File tempFile = temporaryFolder.newFile("test"); - ParseAWSRequest request = - new ParseAWSRequest(ParseHttpRequest.Method.GET, "localhost", tempFile); + ParseFileRequest request = + new ParseFileRequest(ParseHttpRequest.Method.GET, "localhost", tempFile); TestProgressCallback downloadProgressCallback = new TestProgressCallback(); Task task = request.executeAsync(mockHttpClient, null, downloadProgressCallback); diff --git a/Parse/src/test/java/com/parse/ParseRoleTest.java b/Parse/src/test/java/com/parse/ParseRoleTest.java index 53d70e6d0..9440c6ed2 100644 --- a/Parse/src/test/java/com/parse/ParseRoleTest.java +++ b/Parse/src/test/java/com/parse/ParseRoleTest.java @@ -8,8 +8,6 @@ */ package com.parse; -import com.parse.ParseCorePlugins; - import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index 6c20edc38..f8eae0f22 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -50,24 +50,24 @@ // For ParseExecutors.main() @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 23) -public class ParseUserTest { +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) +public class ParseUserTest extends ResetPluginsParseTest { @Rule public ExpectedException thrown= ExpectedException.none(); @Before - public void setUp() { + public void setUp() throws Exception { + super.setUp(); ParseObject.registerSubclass(ParseUser.class); ParseObject.registerSubclass(ParseSession.class); } @After - public void tearDown() { + public void tearDown() throws Exception { + super.tearDown(); ParseObject.unregisterSubclass(ParseUser.class); ParseObject.unregisterSubclass(ParseSession.class); - ParseCorePlugins.getInstance().reset(); - ParsePlugins.reset(); Parse.disableLocalDatastore(); } diff --git a/Parse/src/test/java/com/parse/ResetPluginsParseTest.java b/Parse/src/test/java/com/parse/ResetPluginsParseTest.java new file mode 100644 index 000000000..da19edf81 --- /dev/null +++ b/Parse/src/test/java/com/parse/ResetPluginsParseTest.java @@ -0,0 +1,24 @@ +package com.parse; + +import org.junit.After; +import org.junit.Before; + +/** + * Automatically takes care of setup and teardown of plugins between tests. The order tests run in can be + * different, so if you see a test failing randomly, the test before it may not be tearing down + * properly, and you may need to have the test class subclass this class. + */ +class ResetPluginsParseTest { + + @Before + public void setUp() throws Exception { + ParseCorePlugins.getInstance().reset(); + ParsePlugins.reset(); + } + + @After + public void tearDown() throws Exception { + ParseCorePlugins.getInstance().reset(); + ParsePlugins.reset(); + } +} diff --git a/Parse/src/test/java/com/parse/SubclassTest.java b/Parse/src/test/java/com/parse/SubclassTest.java index 236404413..a1e90b6d5 100644 --- a/Parse/src/test/java/com/parse/SubclassTest.java +++ b/Parse/src/test/java/com/parse/SubclassTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; -public class SubclassTest { +public class SubclassTest extends ResetPluginsParseTest { /** * This is a subclass of ParseObject that will be used below. We're going to imagine a world in * which every "Person" is an instance of "The Flash". @@ -79,6 +79,7 @@ public static class MyUser2 extends ParseUser { @Before public void setUp() throws Exception { + super.setUp(); ParseObject.registerParseSubclasses(); ParseObject.registerSubclass(Person.class); ParseObject.registerSubclass(ClassWithDirtyingConstructor.class); @@ -86,6 +87,7 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { + super.tearDown(); ParseObject.unregisterParseSubclasses(); ParseObject.unregisterSubclass(Person.class); ParseObject.unregisterSubclass(ClassWithDirtyingConstructor.class); diff --git a/Parse/src/test/java/com/parse/TestHelper.java b/Parse/src/test/java/com/parse/TestHelper.java new file mode 100644 index 000000000..4f975ce0f --- /dev/null +++ b/Parse/src/test/java/com/parse/TestHelper.java @@ -0,0 +1,9 @@ +package com.parse; + +/** + * Helper class for testing + */ +public class TestHelper { + + public static final int ROBOLECTRIC_SDK_VERSION = 25; +} diff --git a/ParseStarterProject/build.gradle b/ParseStarterProject/build.gradle index 520fe990d..e0ea8a98e 100644 --- a/ParseStarterProject/build.gradle +++ b/ParseStarterProject/build.gradle @@ -20,8 +20,7 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:25.2.0' + compile "com.android.support:appcompat-v7:$supportLibVersion" - compile 'com.parse.bolts:bolts-tasks:1.4.0' compile project(':Parse') } diff --git a/ParseStarterProject/src/main/java/com/parse/starter/MainActivity.java b/ParseStarterProject/src/main/java/com/parse/starter/MainActivity.java index c3d3d2eb1..f374dac59 100644 --- a/ParseStarterProject/src/main/java/com/parse/starter/MainActivity.java +++ b/ParseStarterProject/src/main/java/com/parse/starter/MainActivity.java @@ -9,14 +9,12 @@ package com.parse.starter; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; -import android.view.Menu; -import android.view.MenuItem; +import android.support.v7.app.AppCompatActivity; import com.parse.ParseAnalytics; -public class MainActivity extends ActionBarActivity { +public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -25,26 +23,4 @@ protected void onCreate(Bundle savedInstanceState) { ParseAnalytics.trackAppOpenedInBackground(getIntent()); } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - int id = item.getItemId(); - - //noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { - return true; - } - - return super.onOptionsItemSelected(item); - } } diff --git a/ParseStarterProject/src/main/res/menu/menu_main.xml b/ParseStarterProject/src/main/res/menu/menu_main.xml deleted file mode 100644 index 3ffc1aa63..000000000 --- a/ParseStarterProject/src/main/res/menu/menu_main.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/build.gradle b/build.gradle index 504a2e38b..2ba3fa882 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' classpath 'com.github.ben-manes:gradle-versions-plugin:0.14.0' } } @@ -20,6 +20,8 @@ ext { compileSdkVersion = 25 buildToolsVersion = "25.0.2" + supportLibVersion = '25.3.1' + minSdkVersion = 9 targetSdkVersion = 25 }