org.peterbaldwin.client.android.delicious.DeliciousApiRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.peterbaldwin.client.android.delicious.DeliciousApiRequest.java

Source

/*-
 *  Copyright (C) 2009 Peter Baldwin   
 *  
 *  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 org.peterbaldwin.client.android.delicious;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;

// TODO: improve error messages
public class DeliciousApiRequest implements Runnable {
    @SuppressWarnings("serial")
    private static class ConnectionException extends IOException {
        private final int mStatusCode;

        private ConnectionException(int statusCode) {
            mStatusCode = statusCode;
        }

        public int getStatusCode() {
            return mStatusCode;
        }
    }

    private static final String LOG_TAG = "DeliciousApiRequest";
    // TODO: make error codes into constructor parameters?
    public static final int HANDLE_AUTH_ERROR = -2;
    public static final int HANDLE_ERROR = -1;
    public static final int HANDLE_DONE = 0;

    public static final int REQUEST_POSTS_ADD = 1;
    public static final int REQUEST_POSTS_SUGGEST = 2;
    public static final int REQUEST_TAGS_GET = 3;

    private static final int BUFFER_SIZE = 1024;

    private final int mRequestType;
    private final int mRequestId;
    private final Uri mUri;
    private final HttpClient mClient;
    private final DeliciousApiResponseHandler mResponseHandler;
    private final Handler mHandler;
    private File mCacheFile;
    private boolean mUseCache;
    private boolean mUpdateCache;
    private boolean mAlwaysUpdateCache;

    DeliciousApiRequest(int requestType, int requestId, Uri uri, HttpClient client,
            DeliciousApiResponseHandler responseHandler, Handler handler) {
        super();
        mRequestType = requestType;
        mRequestId = requestId;
        mUri = uri;
        mClient = client;
        mResponseHandler = responseHandler;
        mHandler = handler;
    }

    private void setError(Message msg, Throwable t) {
        if (mCacheFile != null) {
            mCacheFile.delete();
        }
        String error = String.valueOf(t);
        msg.what = HANDLE_ERROR;
        msg.obj = error;
        Log.e(LOG_TAG, error, t);
    }

    private InputStream readFromCache() throws IOException {
        long start = now();
        try {
            if (mCacheFile == null) {
                throw new NullPointerException();
            }
            if (mCacheFile.exists()) {
                InputStream in = new FileInputStream(mCacheFile);
                in = new BufferedInputStream(in);
                return in;
            }
            return null;
        } finally {
            logTiming("read cache", start);
        }
    }

    private InputStream updateCache(InputStream in) throws IOException {
        long start = now();
        try {
            try {
                if (mCacheFile == null) {
                    throw new NullPointerException();
                }
                File parent = mCacheFile.getParentFile();

                // An exception will be thrown below when trying to write
                // the file if the parent directories were not created.
                parent.mkdirs();

                mCacheFile.createNewFile();
                OutputStream fout = new FileOutputStream(mCacheFile);
                fout = new BufferedOutputStream(fout);
                ByteArrayOutputStream bout = new ByteArrayOutputStream();
                try {
                    byte[] buffer = new byte[BUFFER_SIZE];
                    while (true) {
                        int size = in.read(buffer);
                        if (size < 0) {
                            break;
                        }
                        bout.write(buffer, 0, size);
                        fout.write(buffer, 0, size);
                    }
                } finally {
                    bout.close();
                    fout.close();
                }
                byte[] data = bout.toByteArray();
                return new ByteArrayInputStream(data);
            } finally {
                in.close();
            }
        } finally {
            logTiming("update cache", start);
        }
    }

    private InputStream readFromNetwork() throws IOException {
        long start = now();
        try {
            HttpGet request = new HttpGet(String.valueOf(mUri));
            HttpResponse response = mClient.execute(request);
            StatusLine status = response.getStatusLine();
            int statusCode = status.getStatusCode();
            if (statusCode != HttpStatus.SC_OK) {
                throw new ConnectionException(statusCode);
            }
            HttpEntity entity = response.getEntity();
            InputStream in = entity.getContent();
            in = new BufferedInputStream(in);
            return in;
        } finally {
            logTiming("read network", start);
        }
    }

    private static long now() {
        return SystemClock.uptimeMillis();
    }

    private static void logTiming(String action, long start) {
        long end = now();
        long duration = end - start;
        String msg = String.format("%s took %d ms", action, duration);
        Log.d(LOG_TAG, msg);
    }

    /**
     * {@inheritDoc}
     */
    public void run() {
        Message msg = mHandler.obtainMessage();
        msg.arg1 = mRequestType;
        msg.arg2 = mRequestId;
        msg.what = HANDLE_ERROR;
        msg.obj = "unknown error";
        boolean cacheUpdated = false;
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();
            XMLReader reader = parser.getXMLReader();

            ContentHandler handler = mResponseHandler.getContentHandler();
            reader.setContentHandler(handler);

            InputStream in = null;
            if (in == null && mUseCache && mCacheFile != null) {
                in = readFromCache();
            }
            if (in == null) {
                in = readFromNetwork();

                if (mUpdateCache && mCacheFile != null) {
                    in = updateCache(in);
                    cacheUpdated = true;
                }
            }
            try {
                InputSource input = new InputSource(in);
                long start = now();
                try {
                    reader.parse(input);
                } finally {
                    logTiming("parse", start);
                }
            } finally {
                in.close();
            }
            msg.what = HANDLE_DONE;
            msg.obj = mResponseHandler;
        } catch (ConnectionException e) {
            setError(msg, e);
            int statusCode = e.getStatusCode();
            if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
                msg.what = HANDLE_AUTH_ERROR;
                msg.obj = "invalid username or password";
            } else {
                msg.what = HANDLE_ERROR;
                msg.obj = "unexpected response: " + statusCode;
            }
        } catch (IOException e) {
            setError(msg, e);
        } catch (ParserConfigurationException e) {
            setError(msg, e);
        } catch (SAXException e) {
            setError(msg, e);
        } catch (RuntimeException e) {
            setError(msg, e);
        } catch (Error e) {
            setError(msg, e);
        } finally {
            mHandler.sendMessage(msg);
        }

        if (msg.what != HANDLE_AUTH_ERROR && !cacheUpdated && mAlwaysUpdateCache && mCacheFile != null) {
            // If the authentication is valid, but the cache was not updated,
            // silently update the cache in the background after dispatching the
            // result to the handler.
            try {
                InputStream in = readFromNetwork();
                in = updateCache(in);
                in.close();
                Log.i(LOG_TAG, "cache file updated: " + mCacheFile);
            } catch (IOException e) {
                Log.e(LOG_TAG, "error updating cache", e);
            } catch (RuntimeException e) {
                Log.e(LOG_TAG, "error updating cache", e);
            } catch (Error e) {
                Log.e(LOG_TAG, "error updating cache", e);
            }
        }
    }

    public void setCacheFile(File cacheFile) {
        mCacheFile = cacheFile;
    }

    public void setUseCache(boolean useCache) {
        mUseCache = useCache;
    }

    public void setUpdateCache(boolean updateCache) {
        mUpdateCache = updateCache;
    }

    public void setAlwaysUpdateCache(boolean alwaysUpdateCache) {
        mAlwaysUpdateCache = alwaysUpdateCache;
    }
}