com.vdisk.net.session.AbstractSession.java Source code

Java tutorial

Introduction

Here is the source code for com.vdisk.net.session.AbstractSession.java

Source

package com.vdisk.net.session;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
import org.apache.http.TokenIterator;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.conn.ClientConnectionRequest;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRoute;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.text.TextUtils;
import android.util.Log;

import com.vdisk.net.VDiskAPI;
import com.vdisk.utils.Signature;

/**
 * Keeps track of a logged in user and contains configuration options for the
 * {@link VDiskAPI}. This is a base class to use for creating your own
 * {@link com.vdisk.net.session.Session}s.
 */
public abstract class AbstractSession implements Session {

    private static final String API_SERVER = "api.weipan.cn";
    private static final String UPLOAD_SERVER = "upload-vdisk.sina.com.cn";
    private static final int DEFAULT_MAX_RETRIES = 3;
    /** How long connections are kept alive. */
    private static final int KEEP_ALIVE_DURATION_SECS = 20;
    /** How often the monitoring thread checks for connections to close. */
    private static final int KEEP_ALIVE_MONITOR_INTERVAL_SECS = 5;
    /** The default timeout for client connections. */
    private static final int DEFAULT_TIMEOUT_MILLIS = 30000; // 30 seconds
    // If true, use https to upload a file in the api of "/files_put/"
    public static boolean NEED_HTTPS_UPLOAD = false;
    protected static boolean useWeiboToken;
    protected static Context mContext;
    private final AccessType accessType;
    private final AppKeyPair appKeyPair;
    protected AccessToken accessToken = null;
    protected WeiboAccessToken weiboAccessToken = null;
    private HttpClient client = null;

    /**
     * Creates a new session with the given app key and secret, and access type.
     * The session will not be linked because it has no access token pair.
     */
    public AbstractSession(AppKeyPair appKeyPair, AccessType type) {
        this(appKeyPair, type, null);
    }

    /**
     * Creates a new session with the given app key and secret, and access type.
     * The session will be linked to the account corresponding to the given
     * access token pair.
     */
    public AbstractSession(AppKeyPair appKeyPair, AccessType type, AccessToken accessTokenPair) {
        if (appKeyPair == null)
            throw new IllegalArgumentException("'appKeyPair' must be non-null");
        if (type == null)
            throw new IllegalArgumentException("'type' must be non-null");

        this.appKeyPair = appKeyPair;
        this.accessType = type;
        this.accessToken = accessTokenPair;
    }

    /**
     * Links the session with the given access token and secret.
     */
    public void setAccessTokenPair(AccessToken accessTokenPair) {
        if (accessTokenPair == null)
            throw new IllegalArgumentException("'accessToken' must be non-null");
        this.accessToken = accessTokenPair;
    }

    @Override
    public AppKeyPair getAppKeyPair() {
        return appKeyPair;
    }

    @Override
    public AccessToken getAccessToken() {
        return accessToken;
    }

    @Override
    public AccessToken getWeiboAccessToken() {
        return weiboAccessToken;
    }

    /**
     * ???Token
     *
     * This method is used to set or update Weibo token
     *
     * @param weiboToken
     */
    public void setWeiboAccessToken(WeiboAccessToken weiboToken) {

        this.weiboAccessToken = weiboToken;
    }

    @Override
    public AccessType getAccessType() {
        return accessType;
    }

    @Override
    public boolean isLinked() {

        if (useWeiboToken) {
            if (weiboAccessToken != null && !TextUtils.isEmpty(weiboAccessToken.mAccessToken)) {
                return true;
            }
            return false;
        } else {
            if (accessToken != null && !TextUtils.isEmpty(accessToken.mAccessToken)) {
                return true;
            }
            return false;
        }
    }

    @Override
    public void unlink() {

        if (useWeiboToken) {
            weiboAccessToken = null;
        } else {
            accessToken = null;
        }
        useWeiboToken = false;
    }

    /**
     * Signs the request by using's OAuth's HTTP header authorization scheme and
     * the PLAINTEXT signature method. As such, this should only be used over
     * secure connections (i.e. HTTPS). Using this over regular HTTP connections
     * is completely insecure.
     *
     * @see com.vdisk.net.session.Session#sign
     */
    @Override
    public void sign(HttpRequest request) {

        if (useWeiboToken) {
            // ?Token?
            // Use Weibo token for authentication
            request.setHeader("Authorization",
                    "Weibo " + Signature.getWeiboHeader(this.appKeyPair, this.weiboAccessToken));

            Log.d("weibo_access_Token",
                    "Weibo " + Signature.getWeiboHeader(this.appKeyPair, this.weiboAccessToken));
        } else {
            // Token?
            // Use VDisk token for authentication
            request.setHeader("Authorization", "OAuth2 " + this.accessToken.mAccessToken);
            Log.d("access_Token", this.accessToken.mAccessToken);
        }
        request.setHeader("Host", API_SERVER);
    }

    /**
     * {@inheritDoc} <br/>
     * <br/>
     * The default implementation always returns null.
     */
    @Override
    public synchronized ProxyInfo getProxyInfo() {
        return null;
    }

    /**
     * {@inheritDoc} <br/>
     * <br/>
     * The default implementation does all of this and more, including using a
     * connection pool and killing connections after a timeout to use less
     * battery power on mobile devices. It's unlikely that you'll want to change
     * this behavior.
     */
    @Override
    public synchronized HttpClient getHttpClient() {
        if (client == null) {
            // Set up default connection params. There are two routes to
            // VDisk - api server and content server.
            HttpParams connParams = new BasicHttpParams();
            ConnManagerParams.setMaxConnectionsPerRoute(connParams, new ConnPerRoute() {
                @Override
                public int getMaxForRoute(HttpRoute route) {
                    return 10;
                }
            });
            ConnManagerParams.setMaxTotalConnections(connParams, 20);

            // Set up scheme registry.
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            try {
                schemeRegistry.register(new Scheme("https", TrustAllSSLSocketFactory.getDefault(), 443));
            } catch (Exception e) {
            }

            DBClientConnManager cm = new DBClientConnManager(connParams, schemeRegistry);

            // Set up client params.
            HttpParams httpParams = new BasicHttpParams();
            HttpConnectionParams.setConnectionTimeout(httpParams, DEFAULT_TIMEOUT_MILLIS);
            HttpConnectionParams.setSoTimeout(httpParams, DEFAULT_TIMEOUT_MILLIS);
            HttpConnectionParams.setSocketBufferSize(httpParams, 8192);
            HttpProtocolParams.setUserAgent(httpParams, makeUserAgent());
            httpParams.setParameter(ClientPNames.HANDLE_REDIRECTS, false);

            DefaultHttpClient c = new DefaultHttpClient(cm, httpParams) {
                @Override
                protected ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy() {
                    return new DBKeepAliveStrategy();
                }

                @Override
                protected ConnectionReuseStrategy createConnectionReuseStrategy() {
                    return new DBConnectionReuseStrategy();
                }
            };

            c.addRequestInterceptor(new HttpRequestInterceptor() {
                @Override
                public void process(final HttpRequest request, final HttpContext context)
                        throws HttpException, IOException {
                    if (!request.containsHeader("Accept-Encoding")) {
                        request.addHeader("Accept-Encoding", "gzip");
                    }
                }
            });

            c.addResponseInterceptor(new HttpResponseInterceptor() {
                @Override
                public void process(final HttpResponse response, final HttpContext context)
                        throws HttpException, IOException {
                    HttpEntity entity = response.getEntity();
                    if (entity != null) {
                        Header ceheader = entity.getContentEncoding();
                        if (ceheader != null) {
                            HeaderElement[] codecs = ceheader.getElements();
                            for (HeaderElement codec : codecs) {
                                if (codec.getName().equalsIgnoreCase("gzip")) {
                                    response.setEntity(new GzipDecompressingEntity(response.getEntity()));
                                    return;
                                }
                            }
                        }
                    }
                }
            });

            c.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_MAX_RETRIES));

            client = c;
        }

        return client;
    }

    private String makeUserAgent() {

        String str = "OfficialVdiskAndroidSdk/" + VDiskAPI.SDK_VERSION;
        if (mContext != null) {
            PackageInfo info;
            try {
                info = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
                // ???
                // Version name of current application
                String versionName = info.versionName;
                // ???
                // Package name of current application
                String packageNames = info.packageName;
                str = packageNames + "/" + versionName + " " + str;
            } catch (NameNotFoundException e) {
                e.printStackTrace();
            }
        }
        return str;
    }

    /**
     * {@inheritDoc} <br/>
     * <br/>
     * The default implementation always sets a 30 second timeout.
     */
    @Override
    public void setRequestTimeout(HttpUriRequest request) {
        HttpParams reqParams = request.getParams();
        HttpConnectionParams.setSoTimeout(reqParams, DEFAULT_TIMEOUT_MILLIS);
        HttpConnectionParams.setConnectionTimeout(reqParams, DEFAULT_TIMEOUT_MILLIS);
    }

    @Override
    public String getAPIServer() {
        return API_SERVER;
    }

    @Override
    public String getUploadServer() {
        return UPLOAD_SERVER;
    }

    /**
     * ?Token?
     *
     * Use Weibo token for authentication
     */
    public void enabledWeiboAccessToken() {

        useWeiboToken = true;
    }

    /**
     * ??Token?
     *
     * Use VDisk token for authentication
     */
    public void disabledWeiboAccessToken() {

        useWeiboToken = false;
        weiboAccessToken = null;
    }

    /**
     * ?Token?,?Token
     *
     * Use Weibo token for authentication, and set Weibo token
     */
    public void enabledAndSetWeiboAccessToken(WeiboAccessToken weiboToken) {

        setWeiboAccessToken(weiboToken);
        useWeiboToken = true;
    }

    @Override
    public Context getContext() {
        return mContext;
    }

    public static class TrustAllSSLSocketFactory extends SSLSocketFactory {
        public javax.net.ssl.SSLSocketFactory factory;

        public TrustAllSSLSocketFactory() throws KeyManagementException, NoSuchAlgorithmException,
                KeyStoreException, UnrecoverableKeyException {
            super(null);
            try {
                SSLContext sslcontext = SSLContext.getInstance("TLS");
                sslcontext.init(null, new TrustManager[] { new TrustAllManager() }, null);
                factory = sslcontext.getSocketFactory();
                setHostnameVerifier(new AllowAllHostnameVerifier());
            } catch (Exception ex) {
            }
        }

        public static SocketFactory getDefault() throws KeyManagementException, NoSuchAlgorithmException,
                KeyStoreException, UnrecoverableKeyException {
            return new TrustAllSSLSocketFactory();
        }

        @Override
        public Socket createSocket() throws IOException {
            return factory.createSocket();
        }

        @Override
        public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException {
            return factory.createSocket(socket, s, i, flag);
        }

        public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1, int j) throws IOException {
            return factory.createSocket(inaddr, i, inaddr1, j);
        }

        public Socket createSocket(InetAddress inaddr, int i) throws IOException {
            return factory.createSocket(inaddr, i);
        }

        public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
            return factory.createSocket(s, i, inaddr, j);
        }

        public Socket createSocket(String s, int i) throws IOException {
            return factory.createSocket(s, i);
        }

        public String[] getDefaultCipherSuites() {
            return factory.getDefaultCipherSuites();
        }

        public String[] getSupportedCipherSuites() {
            return factory.getSupportedCipherSuites();
        }
    }

    public static class TrustAllManager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] cert, String authType) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] cert, String authType) throws CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }

    private static class DBKeepAliveStrategy implements ConnectionKeepAliveStrategy {
        @Override
        public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
            // Keep-alive for the shorter of 20 seconds or what the server
            // specifies.
            long timeout = KEEP_ALIVE_DURATION_SECS * 1000;

            HeaderElementIterator i = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (i.hasNext()) {
                HeaderElement element = i.nextElement();
                String name = element.getName();
                String value = element.getValue();
                if (value != null && name.equalsIgnoreCase("timeout")) {
                    try {
                        timeout = Math.min(timeout, Long.parseLong(value) * 1000);
                    } catch (NumberFormatException e) {
                    }
                }
            }

            return timeout;
        }
    }

    private static class DBConnectionReuseStrategy extends DefaultConnectionReuseStrategy {

        /**
         * Implements a patch out in 4.1.x and 4.2 that isn't available in 4.0.x
         * which fixes a bug where connections aren't reused when the response
         * is gzipped. See https://issues.apache.org/jira/browse/HTTPCORE-257
         * for info about the issue, and
         * http://svn.apache.org/viewvc?view=revision&revision=1124215 for the
         * patch which is copied here.
         */
        @Override
        public boolean keepAlive(final HttpResponse response, final HttpContext context) {
            if (response == null) {
                throw new IllegalArgumentException("HTTP response may not be null.");
            }
            if (context == null) {
                throw new IllegalArgumentException("HTTP context may not be null.");
            }

            // Check for a self-terminating entity. If the end of the entity
            // will
            // be indicated by closing the connection, there is no keep-alive.
            ProtocolVersion ver = response.getStatusLine().getProtocolVersion();
            Header teh = response.getFirstHeader(HTTP.TRANSFER_ENCODING);
            if (teh != null) {
                if (!HTTP.CHUNK_CODING.equalsIgnoreCase(teh.getValue())) {
                    return false;
                }
            } else {
                Header[] clhs = response.getHeaders(HTTP.CONTENT_LEN);
                // Do not reuse if not properly content-length delimited
                if (clhs == null || clhs.length != 1) {
                    return false;
                }
                Header clh = clhs[0];
                try {
                    int contentLen = Integer.parseInt(clh.getValue());
                    if (contentLen < 0) {
                        return false;
                    }
                } catch (NumberFormatException ex) {
                    return false;
                }
            }

            // Check for the "Connection" header. If that is absent, check for
            // the "Proxy-Connection" header. The latter is an unspecified and
            // broken but unfortunately common extension of HTTP.
            HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE);
            if (!hit.hasNext())
                hit = response.headerIterator("Proxy-Connection");

            // Experimental usage of the "Connection" header in HTTP/1.0 is
            // documented in RFC 2068, section 19.7.1. A token "keep-alive" is
            // used to indicate that the connection should be persistent.
            // Note that the final specification of HTTP/1.1 in RFC 2616 does
            // not
            // include this information. Neither is the "Connection" header
            // mentioned in RFC 1945, which informally describes HTTP/1.0.
            //
            // RFC 2616 specifies "close" as the only connection token with a
            // specific meaning: it disables persistent connections.
            //
            // The "Proxy-Connection" header is not formally specified anywhere,
            // but is commonly used to carry one token, "close" or "keep-alive".
            // The "Connection" header, on the other hand, is defined as a
            // sequence of tokens, where each token is a header name, and the
            // token "close" has the above-mentioned additional meaning.
            //
            // To get through this mess, we treat the "Proxy-Connection" header
            // in exactly the same way as the "Connection" header, but only if
            // the latter is missing. We scan the sequence of tokens for both
            // "close" and "keep-alive". As "close" is specified by RFC 2068,
            // it takes precedence and indicates a non-persistent connection.
            // If there is no "close" but a "keep-alive", we take the hint.

            if (hit.hasNext()) {
                try {
                    TokenIterator ti = createTokenIterator(hit);
                    boolean keepalive = false;
                    while (ti.hasNext()) {
                        final String token = ti.nextToken();
                        if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
                            return false;
                        } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) {
                            // continue the loop, there may be a "close"
                            // afterwards
                            keepalive = true;
                        }
                    }
                    if (keepalive)
                        return true;
                    // neither "close" nor "keep-alive", use default policy

                } catch (ParseException px) {
                    // invalid connection header means no persistent connection
                    // we don't have logging in HttpCore, so the exception is
                    // lost
                    return false;
                }
            }

            // default since HTTP/1.1 is persistent, before it was
            // non-persistent
            return !ver.lessEquals(HttpVersion.HTTP_1_0);
        }
    }

    private static class DBClientConnManager extends ThreadSafeClientConnManager {
        public DBClientConnManager(HttpParams params, SchemeRegistry schreg) {
            super(params, schreg);
        }

        @Override
        public ClientConnectionRequest requestConnection(HttpRoute route, Object state) {
            IdleConnectionCloserThread.ensureRunning(this, KEEP_ALIVE_DURATION_SECS,
                    KEEP_ALIVE_MONITOR_INTERVAL_SECS);
            return super.requestConnection(route, state);
        }
    }

    private static class IdleConnectionCloserThread extends Thread {
        private static IdleConnectionCloserThread thread = null;
        private final DBClientConnManager manager;
        private final int idleTimeoutSeconds;
        private final int checkIntervalMs;

        public IdleConnectionCloserThread(DBClientConnManager manager, int idleTimeoutSeconds,
                int checkIntervalSeconds) {
            super();
            this.manager = manager;
            this.idleTimeoutSeconds = idleTimeoutSeconds;
            this.checkIntervalMs = checkIntervalSeconds * 1000;
        }

        public static synchronized void ensureRunning(DBClientConnManager manager, int idleTimeoutSeconds,
                int checkIntervalSeconds) {
            if (thread == null) {
                thread = new IdleConnectionCloserThread(manager, idleTimeoutSeconds, checkIntervalSeconds);
                thread.start();
            }
        }

        @Override
        public void run() {
            try {
                while (true) {
                    synchronized (this) {
                        wait(checkIntervalMs);
                    }
                    manager.closeExpiredConnections();
                    manager.closeIdleConnections(idleTimeoutSeconds, TimeUnit.SECONDS);
                    synchronized (IdleConnectionCloserThread.class) {
                        if (manager.getConnectionsInPool() == 0) {
                            thread = null;
                            return;
                        }
                    }
                }
            } catch (InterruptedException e) {
                thread = null;
            }
        }
    }

    private static class GzipDecompressingEntity extends HttpEntityWrapper {

        /*
         * From Apache HttpClient Examples.
         *
         * ====================================================================
         * Licensed to the Apache Software Foundation (ASF) under one or more
         * contributor license agreements. See the NOTICE file distributed with
         * this work for additional information regarding copyright ownership.
         * The ASF licenses this file to you 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.
         * ====================================================================
         *
         * This software consists of voluntary contributions made by many
         * individuals on behalf of the Apache Software Foundation. For more
         * information on the Apache Software Foundation, please see
         * <http://www.apache.org/>.
         */

        public GzipDecompressingEntity(final HttpEntity entity) {
            super(entity);
        }

        @Override
        public InputStream getContent() throws IOException, IllegalStateException {

            // the wrapped entity's getContent() decides about repeatability
            InputStream wrappedin = wrappedEntity.getContent();

            return new GZIPInputStream(wrappedin);
        }

        @Override
        public long getContentLength() {
            // length of ungzipped content is not known
            return -1;
        }
    }
}