com.github.marcosalis.kraken.utils.http.DefaultHttpRequestsManager.java Source code

Java tutorial

Introduction

Here is the source code for com.github.marcosalis.kraken.utils.http.DefaultHttpRequestsManager.java

Source

/*
 * Copyright 2013 Luluvise Ltd
 * Copyright 2013 Marco Salis - fast3r(at)gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.marcosalis.kraken.utils.http;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import javax.annotation.concurrent.ThreadSafe;

import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Application;
import android.util.Log;

import com.github.marcosalis.kraken.DroidConfig;
import com.google.api.client.extensions.android.AndroidUtils;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.util.Preconditions;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;

/**
 * Default implementation of {@link HttpRequestsManager}, a decorator around the
 * Google library {@link HttpRequestFactory} to build requests using the same
 * transport and optimizing reusable components. It just takes care of using the
 * same {@link HttpTransport} for any connection and uses an
 * {@link HttpRequestFactory} initialised with default connection parameters,
 * user agent and timeouts.
 * 
 * The global instance, suitable for most uses, can be retrieved by calling
 * {@link #get()}, and {@link #initialize(ConnectionKeepAliveStrategy)} must be
 * called before accessing the request facory.
 * 
 * Please note that no limits are put on the number of concurrent connection at
 * this abstraction level. Clients must implement their own pooling mechanism to
 * limit it or just rely on the wrapped connection library pooling policies.
 * 
 * To debug network connections executed from the {@link HttpTransport} library
 * in a device or emulator do the following:
 * 
 * <ul>
 * <li>set the DEBUG flag to true in the {@link DroidConfig} class</li>
 * <li>add
 * <i>Logger.getLogger(HttpTransport.class.getName()).setLevel(Level.CONFIG
 * );</i> before the execute call in your code</li>
 * <li>type <i>adb shell setprop log.tag.HttpTransport DEBUG</i> in a terminal
 * to enable debug logging for the transport class in the connected device or
 * emulator</li>
 * </ul>
 * 
 * @since 1.0
 * @author Marco Salis
 */
@Beta
@ThreadSafe
public class DefaultHttpRequestsManager implements HttpRequestsManager {

    private static final String TAG = DefaultHttpRequestsManager.class.getSimpleName();

    /**
     * Globally accessible instance of the default HTTP connection manager.
     */
    private static final DefaultHttpRequestsManager INSTANCE = new DefaultHttpRequestsManager();

    private volatile HttpRequestFactory mDefaultRequestFactory;

    /**
     * Shortcut method to return the {@link DefaultHttpRequestsManager} global
     * instance.
     */
    public static DefaultHttpRequestsManager get() {
        return INSTANCE;
    }

    private DefaultHttpRequestsManager() {
        final Level logLevel = DroidConfig.DEBUG ? Level.CONFIG : Level.OFF;
        Logger.getLogger(HttpTransport.class.getName()).setLevel(logLevel);
    }

    /**
     * Initializes the {@link DefaultHttpRequestsManager}. Call this preferably
     * from the {@link Application#onCreate()} method.
     * 
     * @param strategy
     *            The {@link ConnectionKeepAliveStrategy} if
     *            {@link ApacheHttpTransport} is used.
     */
    @OverridingMethodsMustInvokeSuper
    public synchronized void initialize(@Nonnull ConnectionKeepAliveStrategy strategy) {

        if (DroidConfig.DEBUG) { // logging system properties values
            Log.d(TAG, "http.maxConnections: " + System.getProperty("http.maxConnections"));
            Log.d(TAG, "http.keepAlive: " + System.getProperty("http.keepAlive"));
        }
        /*
         * Get the best HTTP client for the current Android version, mimicking
         * the behavior of the method AndroidHttp.newCompatibleTransport(). As
         * of now, ApacheHttpTransport appears to be much faster and less
         * CPU-consuming than NetHttpTransport on Gingerbread, so we use the
         * latter only for API >= 11
         */
        if (AndroidUtils.isMinimumSdkLevel(11)) {
            // use NetHttpTransport as default connection transport
            mDefaultRequestFactory = createRequestFactory(new NetHttpTransport());
        } else {
            /* Use custom DefaultHttpClient to set the keep alive strategy */
            final DefaultHttpClient httpClient = ApacheHttpTransport.newDefaultHttpClient();
            if (strategy != null) {
                httpClient.setKeepAliveStrategy(strategy);
            }
            /**
             * Android has a known issue that causes the generation of unsafe
             * {@link SecureRandom} values which can affect secure connections
             * with the Apache http library. See the link below for more
             * information.
             * 
             * <pre>
             * http://android-developers.blogspot.com.au/2013/08/some-securerandom-thoughts.html
             * </pre>
             */
            mDefaultRequestFactory = createRequestFactory(new ApacheHttpTransport(httpClient));
        }
    }

    /**
     * Same as {@link #initialize(ConnectionKeepAliveStrategy)} with a
     * {@link DefaultConnectionKeepAliveStrategy}.
     */
    public synchronized void initialize() {
        initialize(new DefaultConnectionKeepAliveStrategy());
    }

    /**
     * <b>Only for testing purposes.</b><br>
     * Inject a custom {@link HttpTransport} inside the manager
     * 
     * @param transport
     *            The {@link HttpTransport} to inject
     */
    @VisibleForTesting
    synchronized void injectTransport(@Nonnull HttpTransport transport) {
        mDefaultRequestFactory = createRequestFactory(transport);
    }

    /**
     * {@inheritDoc}
     * 
     * The HttpRequest objects created with this factory don't throw exceptions
     * if the request is not successful (response < 200 or >299), so you have to
     * check the HTTP result code within the response.
     */
    @Nonnull
    @Override
    public HttpRequestFactory getRequestFactory() {
        Preconditions.checkArgument(mDefaultRequestFactory != null, "initialize() not called");
        return mDefaultRequestFactory;
    }

    /**
     * {@inheritDoc}
     */
    @Nonnull
    @Override
    public HttpRequestFactory createRequestFactory(@Nonnull HttpTransport transport) {
        return transport.createRequestFactory(new DefaultHttpRequestInitializer());
    }

    /**
     * {@inheritDoc}
     */
    @Nonnull
    @Override
    public HttpRequest buildRequest(@Nonnull String method, @Nonnull String urlString,
            @Nullable HttpContent content) throws IOException {
        Preconditions.checkArgument(mDefaultRequestFactory != null, "initialize() not called");
        return mDefaultRequestFactory.buildRequest(method, new GenericUrl(urlString), content);
    }

}