de.unwesen.packrat.api.APIBase.java Source code

Java tutorial

Introduction

Here is the source code for de.unwesen.packrat.api.APIBase.java

Source

/**
 * This file is part of PackRat, an Android app for managing media collections.
 * Copyright (C) 2009-2012 Jens Finkhaeuser <jens@finkhaeuser.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.unwesen.packrat.api;

import android.net.Uri;

import android.content.Context;
import android.content.Intent;

import java.io.InputStream;
import java.io.ByteArrayOutputStream;

import java.net.URI;
import java.io.IOException;

import android.os.Handler;
import android.os.Message;

import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.params.HttpParams;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.HttpVersion;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.auth.AuthScope;

import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.HttpResponse;
import org.apache.http.HttpEntity;

import android.database.Cursor;

import java.util.StringTokenizer;
import java.text.SimpleDateFormat;

import android.util.Log;

/**
 * Shared functionality for all backend API classes, i.e. BackendAPI and
 * Tracker.
 * - Sets up HTTP client
 * - Includes helper functions for IO and parsing.
 * - etc.
 *
 * Most data members are static as there is technically no need for more than
 * one APIBase instance per process. On the other hand, we do need a Context,
 * so making the APIBase a singleton is impractical.
 **/
public class APIBase {
    /***************************************************************************
     * Public constants
     **/
    // Log ID
    public static final String LTAG = "APIBase";

    // Error message IDs.
    public static final int ERR_SUCCESS = 0;
    public static final int ERR_EMPTY_RESPONSE = 1;
    public static final int ERR_TRANSMISSION = 2;
    public static final int ERR_SERIALIZATION = 3;
    public static final int ERR_SERVER = 4;
    public static final int ERR_UNKNOWN = 5;

    // Referer URL, required for some APIs.
    public static final String REFERER_URL = "http://packrat-app.unwesen.de/";

    /***************************************************************************
     * Private constants
     **/
    // HTTP Client parameters
    // XXX These values are pretty much copied from example code and likely need
    //     to be tweaked.
    private static final int HTTP_TIMEOUT = 60 * 1000;
    private static final HttpVersion HTTP_VERSION = HttpVersion.HTTP_1_1;

    // Requester startup delay. Avoids high load at startup that could impact UX.
    private static final int REQUESTER_STARTUP_DELAY = 1000;

    // Response processing
    private static final int READ_BUFFER_SIZE = 8192;

    /***************************************************************************
     * Protected static data
     **/
    protected static DefaultHttpClient sClient;
    protected static ThreadSafeClientConnManager sConnectionManager;

    /***************************************************************************
     * Helper class for fetching API responses in the background.
     **/
    protected class Requester extends Thread {
        public boolean keepRunning = true;

        protected Uri mUri;
        protected Handler mHandler;

        public Requester(Uri uri, Handler handler) {
            super();
            mUri = uri;
            mHandler = handler;
        }

        @Override
        public void run() {
            // Delay before starting to fetch stuff.
            if (mDelayStartup) {
                try {
                    sleep(REQUESTER_STARTUP_DELAY);
                    mDelayStartup = false;
                } catch (java.lang.InterruptedException ex) {
                    return;
                }
            }

            // The loop ensures that external interrrupts don't mean the request is
            // never executed..
            while (keepRunning) {
                fetch();
                keepRunning = false;
            }
        }

        /**
         * Fetch raw bytes by default.
         **/
        public void fetch() {
            // Construct request.
            // TODO distinguish between creating get/post requests; probably
            // requires the Requester interface to not accept Uris but rather
            // API-strings, and params, etc.
            HttpGet request = new HttpGet(mUri.toString());
            request.addHeader("Referer", REFERER_URL);

            // Perform request.
            byte[] data = fetchRawSynchronous(request, mHandler);
            if (null != data) {
                mHandler.obtainMessage(ERR_SUCCESS, new String(data)).sendToTarget();
            }
        }
    }

    /***************************************************************************
     * Custom Requester for fetching XML documents. Instead of passing raw
     * string data, it passes a HttpEntity.
     **/
    protected class XMLRequester extends Requester {
        public XMLRequester(Uri uri, Handler handler) {
            super(uri, handler);
        }

        public void fetch() {
            // Construct request
            HttpGet request = new HttpGet(mUri.toString());
            request.addHeader("Referer", REFERER_URL);

            HttpResponse response;
            try {
                response = sClient.execute(request);

                // Read response
                HttpEntity entity = response.getEntity();
                if (null == entity) {
                    mHandler.obtainMessage(ERR_EMPTY_RESPONSE).sendToTarget();
                    return;
                }
                mHandler.obtainMessage(ERR_SUCCESS, entity.getContent()).sendToTarget();

            } catch (IOException ex) {
                Log.w(LTAG, "Error fetching XML document: " + ex);
                mHandler.obtainMessage(ERR_TRANSMISSION).sendToTarget();
            } catch (Exception ex) {
                Log.e(LTAG, "Unknown exception: " + ex);
                mHandler.obtainMessage(ERR_UNKNOWN).sendToTarget();
            }
        }
    }

    /***************************************************************************
        
    }
        
        
        
    /***************************************************************************
     * Protected data members
     **/
    protected Context mContext;

    // Requester. There's only one instance, so only one API call can be scheduled
    // at a time.
    private Requester mRequester;
    private boolean mDelayStartup = true;

    /***************************************************************************
     * Implementation
     **/
    public APIBase(Context context) {
        mContext = context;

        if (null == sConnectionManager || null == sClient) {
            // Set up an HttpClient instance that can be used by multiple threads
            HttpParams params = defaultHttpParams();

            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

            sConnectionManager = new ThreadSafeClientConnManager(params, registry);
            sClient = new DefaultHttpClient(sConnectionManager, params);
        }
    }

    /**
     * Request the given Uri. Send ERR_SUCCESS and a response string on success,
     * else other message IDs and null for the object.
     * Interrupts/cancels any requests currently in flight.
     **/
    public void requestUri(Uri uri, Handler result_handler) {
        startRequester(new Requester(uri, result_handler));
    }

    /**
     * Start the given Requester.
     **/
    protected void startRequester(Requester requester) {
        if (null != mRequester) {
            mRequester.keepRunning = false;
            mRequester.interrupt();
        }

        mRequester = requester;
        mRequester.start();
    }

    /**
     * Converts an InputStream to a String containing the InputStream's content.
     **/
    public static String readStream(InputStream is) {
        try {
            return new String(readStreamRaw(is));
        } catch (IOException ex) {
            Log.e(LTAG, "Error: " + ex.getMessage());
            return null;
        }
    }

    /**
     * Converts an InputStream to a byte array containing the InputStream's content.
     **/
    public static byte[] readStreamRaw(InputStream is) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream(READ_BUFFER_SIZE);
        byte[] bytes = new byte[READ_BUFFER_SIZE];

        try {
            // Read bytes from the input stream in chunks and write
            // them into the output stream
            int bytes_read = 0;
            while (-1 != (bytes_read = is.read(bytes))) {
                os.write(bytes, 0, bytes_read);
            }

            byte[] retval = os.toByteArray();

            is.close();
            os.close();

            return retval;
        } catch (java.io.IOException ex) {
            Log.e(LTAG, "Could not read input stream: " + ex.getMessage());
        }
        return null;
    }

    public byte[] fetchRawSynchronous(HttpRequestBase request, Handler handler) {
        HttpResponse response;
        try {
            response = sClient.execute(request);

            // Read response
            HttpEntity entity = response.getEntity();
            if (null == entity) {
                Log.e(LTAG, "Response is empty: " + request.getURI().toString());
                if (null != handler) {
                    handler.obtainMessage(ERR_EMPTY_RESPONSE).sendToTarget();
                }
                return null;
            }

            return readStreamRaw(entity.getContent());

        } catch (IOException ex) {
            Log.w(LTAG, "An exception occurred when reading the API response: " + ex.getMessage());
            if (null != handler) {
                handler.obtainMessage(ERR_TRANSMISSION).sendToTarget();
            }
            return null;
        }
    }

    /**
     * Returns a SimpleDateFormat instance initialized to parse/format UTC
     * timestamps according to ISO 8601.
     **/
    public static SimpleDateFormat ISO8601Format() {
        return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    }

    /**
     * Returns a set of default HTTP parameters.
     **/
    protected static HttpParams defaultHttpParams() {
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(params, HTTP_TIMEOUT);
        HttpProtocolParams.setVersion(params, HTTP_VERSION);
        return params;
    }
}