com.deliciousdroid.client.DeliciousApi.java Source code

Java tutorial

Introduction

Here is the source code for com.deliciousdroid.client.DeliciousApi.java

Source

/*
 * DeliciousDroid - http://code.google.com/p/DeliciousDroid/
 *
 * Copyright (C) 2010 Matt Schmidt
 *
 * DeliciousDroid 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.
 *
 * DeliciousDroid 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 DeliciousDroid; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

package com.deliciousdroid.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.net.Uri;
import android.util.Log;

import com.deliciousdroid.Constants;
import com.deliciousdroid.providers.BookmarkContent.Bookmark;
import com.deliciousdroid.providers.BundleContent.Bundle;
import com.deliciousdroid.providers.TagContent.Tag;
import com.deliciousdroid.xml.SaxBookmarkParser;
import com.deliciousdroid.xml.SaxBundleParser;
import com.deliciousdroid.xml.SaxTagParser;
import com.deliciousdroid.client.TooManyRequestsException;
import com.deliciousdroid.client.Update;

public class DeliciousApi {

    private static final String TAG = "DeliciousApi";

    public static final String USER_AGENT = "AuthenticationService/1.0";
    public static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms

    public static final String FETCH_TAGS_URI = "v1/tags/get";
    public static final String FETCH_BUNDLES_URI = "v1/tags/bundles/all";
    public static final String FETCH_SUGGESTED_TAGS_URI = "v1/posts/suggest";
    public static final String FETCH_BOOKMARKS_URI = "v1/posts/all";
    public static final String FETCH_CHANGED_BOOKMARKS_URI = "v1/posts/all";
    public static final String FETCH_BOOKMARK_URI = "v1/posts/get";
    public static final String LAST_UPDATE_URI = "v1/posts/update";
    public static final String DELETE_BOOKMARK_URI = "v1/posts/delete";
    public static final String ADD_BOOKMARKS_URI = "v1/posts/add";

    private static final String SCHEME = "http";
    private static final String SCHEME_HTTP = "http";
    private static final String DELICIOUS_AUTHORITY = "api.del.icio.us";
    private static final int PORT = 80;

    private static final AuthScope SCOPE = new AuthScope(DELICIOUS_AUTHORITY, PORT);

    /**
     * Gets timestamp of last update to data on Delicious servers.
     * 
     * @param account The account being synced.
     * @param context The current application context.
     * @return An Update object containing the timestamp and the number of new bookmarks in the
     * inbox.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static Update lastUpdate(Account account, Context context)
            throws IOException, AuthenticationException, TooManyRequestsException {

        String response = null;
        InputStream responseStream = null;
        TreeMap<String, String> params = new TreeMap<String, String>();
        Update update = null;

        responseStream = DeliciousApiCall(LAST_UPDATE_URI, params, account, context);
        response = convertStreamToString(responseStream);
        responseStream.close();

        try {
            if (response.contains("<?xml")) {
                update = Update.valueOf(response);
            } else {
                Log.e(TAG, "Server error in fetching bookmark list");
                throw new IOException();
            }
        } catch (Exception e) {
            Log.e(TAG, "Server error in fetching bookmark list");
            throw new IOException();
        }
        return update;
    }

    /**
     * Sends a request to Delicious's Add Bookmark api.
     * 
     * @param bookmark The bookmark to be added.
     * @param account The account being synced.
     * @param context The current application context.
     * @return A boolean indicating whether or not the api call was successful.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     * @throws TokenRejectedException If the oauth token is reported to be expired.
     * @throws Exception If an unknown error is encountered.
     */
    public static Boolean addBookmark(Bookmark bookmark, Account account, Context context)
            throws IOException, AuthenticationException, TooManyRequestsException {

        String url = bookmark.getUrl();
        if (url.endsWith("/")) {
            url = url.substring(0, url.lastIndexOf('/'));
        }

        TreeMap<String, String> params = new TreeMap<String, String>();

        params.put("description", bookmark.getDescription());
        params.put("extended", bookmark.getNotes());

        // until delicious fixes their api we need to use commas to delimit tags
        // tags with spaces, although supported by the web interface, will not work with deliciousdroid
        params.put("tags", bookmark.getTagString().replace(' ', ','));
        params.put("url", bookmark.getUrl());
        params.put("replace", "yes");

        if (bookmark.getShared()) {
            params.put("shared", "yes");
        } else
            params.put("shared", "no");

        String uri = ADD_BOOKMARKS_URI;
        String response = null;
        InputStream responseStream = null;

        responseStream = DeliciousApiCall(uri, params, account, context);
        response = convertStreamToString(responseStream);
        responseStream.close();

        if (response.contains("<result code=\"done\"/>")) {
            return true;
        } else {
            Log.e(TAG, "Server error in adding bookmark");
            throw new IOException();
        }
    }

    /**
     * Sends a request to Delicious's Delete Bookmark api.
     * 
     * @param bookmark The bookmark to be deleted.
     * @param account The account being synced.
     * @param context The current application context.
     * @return A boolean indicating whether or not the api call was successful.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static Boolean deleteBookmark(Bookmark bookmark, Account account, Context context)
            throws IOException, AuthenticationException, TooManyRequestsException {

        TreeMap<String, String> params = new TreeMap<String, String>();
        String response = null;
        InputStream responseStream = null;
        String url = DELETE_BOOKMARK_URI;

        params.put("url", bookmark.getUrl());

        responseStream = DeliciousApiCall(url, params, account, context);
        response = convertStreamToString(responseStream);
        responseStream.close();

        if (response.contains("<result code=\"done\"") || response.contains("<result code=\"item not found\"")) {
            return true;
        } else {
            Log.e(TAG, "Server error in fetching bookmark list");
            throw new IOException();
        }
    }

    /**
     * Retrieves a specific list of bookmarks from Delicious.
     * 
     * @param hashes A list of bookmark hashes to be retrieved.  
     *    The hashes are MD5 hashes of the URL of the bookmark.
     * 
     * @param account The account being synced.
     * @param context The current application context.
     * @return A list of bookmarks received from the server.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static ArrayList<Bookmark> getBookmark(ArrayList<String> hashes, Account account, Context context)
            throws IOException, AuthenticationException, TooManyRequestsException {

        ArrayList<Bookmark> bookmarkList = new ArrayList<Bookmark>();
        TreeMap<String, String> params = new TreeMap<String, String>();
        String hashString = "";
        InputStream responseStream = null;
        String url = FETCH_BOOKMARK_URI;

        for (String h : hashes) {
            if (hashes.get(0) != h) {
                hashString += "+";
            }
            hashString += h;
        }
        params.put("meta", "yes");
        params.put("hashes", hashString);

        responseStream = DeliciousApiCall(url, params, account, context);
        SaxBookmarkParser parser = new SaxBookmarkParser(responseStream);

        try {
            bookmarkList = parser.parse();
        } catch (ParseException e) {
            Log.e(TAG, "Server error in fetching bookmark list");
            throw new IOException();
        }

        responseStream.close();
        return bookmarkList;
    }

    /**
     * Retrieves the entire list of bookmarks for a user from Pinboard.
     * 
     * @param tagname If specified, will only retrieve bookmarks with a specific tag.
     * @param account The account being synced.
     * @param context The current application context.
     * @return A list of bookmarks received from the server.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     * @throws TooManyRequestsException 
     */
    public static ArrayList<Bookmark> getAllBookmarks(String tagName, Account account, Context context)
            throws IOException, AuthenticationException, TooManyRequestsException {

        return getAllBookmarks(tagName, 0, 0, account, context);
    }

    /**
     * Retrieves the entire list of bookmarks for a user from Delicious.  Warning:  Overuse of this 
     * api call will get your account throttled.
     * 
     * @param tagname If specified, will only retrieve bookmarks with a specific tag.
     * @param account The account being synced.
     * @param context The current application context.
     * @return A list of bookmarks received from the server.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static ArrayList<Bookmark> getAllBookmarks(String tagName, int start, int count, Account account,
            Context context) throws IOException, AuthenticationException, TooManyRequestsException {
        ArrayList<Bookmark> bookmarkList = new ArrayList<Bookmark>();

        InputStream responseStream = null;
        TreeMap<String, String> params = new TreeMap<String, String>();
        String url = FETCH_BOOKMARKS_URI;

        if (tagName != null && tagName != "") {
            params.put("tag", tagName);
        }

        if (start != 0) {
            params.put("start", Integer.toString(start));
        }

        if (count != 0) {
            params.put("results", Integer.toString(count));
        }

        params.put("meta", "yes");

        responseStream = DeliciousApiCall(url, params, account, context);
        SaxBookmarkParser parser = new SaxBookmarkParser(responseStream);

        //Log.d("kdf", convertStreamToString(responseStream));

        try {
            bookmarkList = parser.parse();
        } catch (ParseException e) {
            Log.e(TAG, "Server error in fetching bookmark list");
            throw new IOException();
        }

        responseStream.close();

        return bookmarkList;
    }

    /**
     * Retrieves a list of all bookmarks, with only their URL hash and a change (meta) hash,
     * to determine what bookmarks have changed since the last update.
     * 
     * @param account The account being synced.
     * @param context The current application context.
     * @return A list of bookmarks received from the server with only the URL hash and meta hash.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static ArrayList<Bookmark> getChangedBookmarks(Account account, Context context)
            throws IOException, AuthenticationException {

        ArrayList<Bookmark> bookmarkList = new ArrayList<Bookmark>();
        InputStream responseStream = null;
        TreeMap<String, String> params = new TreeMap<String, String>();
        String url = FETCH_CHANGED_BOOKMARKS_URI;

        params.put("hashes", "yes");

        responseStream = DeliciousApiCall(url, params, account, context);
        SaxBookmarkParser parser = new SaxBookmarkParser(responseStream);

        try {
            bookmarkList = parser.parse();
        } catch (ParseException e) {
            Log.e(TAG, "Server error in fetching bookmark list");
            throw new IOException();
        }

        responseStream.close();
        return bookmarkList;
    }

    /**
     * Retrieves a list of suggested tags for a URL.
     * 
     * @param suggestUrl The URL to get suggested tags for.
     * @param account The account being synced.
     * @param context The current application context.
     * @return A list of tags suggested for the provided url.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static ArrayList<Tag> getSuggestedTags(String suggestUrl, Account account, Context context)
            throws IOException, AuthenticationException, TooManyRequestsException {

        ArrayList<Tag> tagList = new ArrayList<Tag>();

        if (!suggestUrl.startsWith("http")) {
            suggestUrl = "http://" + suggestUrl;
        }

        InputStream responseStream = null;
        TreeMap<String, String> params = new TreeMap<String, String>();
        params.put("url", suggestUrl);

        String url = FETCH_SUGGESTED_TAGS_URI;

        responseStream = DeliciousApiCall(url, params, account, context);
        SaxTagParser parser = new SaxTagParser(responseStream);

        try {
            tagList = parser.parseSuggested();
        } catch (ParseException e) {
            Log.e(TAG, "Server error in fetching bookmark list");
            throw new IOException();
        }

        responseStream.close();
        return tagList;
    }

    /**
     * Retrieves a list of all tags for a user from Delicious.
     * 
     * @param account The account being synced.
     * @param context The current application context.
     * @return A list of the users tags.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static ArrayList<Tag> getTags(Account account, Context context)
            throws IOException, AuthenticationException, TooManyRequestsException {

        ArrayList<Tag> tagList = new ArrayList<Tag>();

        InputStream responseStream = null;
        final TreeMap<String, String> params = new TreeMap<String, String>();

        responseStream = DeliciousApiCall(FETCH_TAGS_URI, params, account, context);
        final SaxTagParser parser = new SaxTagParser(responseStream);

        try {
            tagList = parser.parse();
        } catch (ParseException e) {
            Log.e(TAG, "Server error in fetching bookmark list");
            throw new IOException();
        }

        responseStream.close();
        return tagList;
    }

    /**
     * Retrieves a list of all tag bundles for a user from Delicious.
     * 
     * @param account The account being synced.
     * @param context The current application context.
     * @return A list of the users bundles.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    public static ArrayList<Bundle> getBundles(Account account, Context context)
            throws IOException, AuthenticationException {

        ArrayList<Bundle> bundleList = new ArrayList<Bundle>();
        InputStream responseStream = null;
        TreeMap<String, String> params = new TreeMap<String, String>();
        String url = FETCH_BUNDLES_URI;

        responseStream = DeliciousApiCall(url, params, account, context);
        SaxBundleParser parser = new SaxBundleParser(responseStream);

        try {
            bundleList = parser.parse();
        } catch (ParseException e) {
            Log.e(TAG, "Server error in fetching bundle list");
            throw new IOException();
        }

        responseStream.close();
        return bundleList;
    }

    /**
     * Performs an api call to Delicious's http based api methods.
     * 
     * @param url URL of the api method to call.
     * @param params Extra parameters included in the api call, as specified by different methods.
     * @param account The account being synced.
     * @param context The current application context.
     * @return A String containing the response from the server.
     * @throws IOException If a server error was encountered.
     * @throws AuthenticationException If an authentication error was encountered.
     */
    private static InputStream DeliciousApiCall(String url, TreeMap<String, String> params, Account account,
            Context context) throws IOException, AuthenticationException {

        final AccountManager am = AccountManager.get(context);

        if (account == null)
            throw new AuthenticationException();

        final String username = account.name;
        String authtoken = null;

        try {
            authtoken = am.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE, false);
        } catch (OperationCanceledException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (AuthenticatorException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Uri.Builder builder = new Uri.Builder();
        builder.scheme(SCHEME);
        builder.authority(DELICIOUS_AUTHORITY);
        builder.appendEncodedPath(url);
        for (String key : params.keySet()) {
            builder.appendQueryParameter(key, params.get(key));
        }

        String apiCallUrl = builder.build().toString();

        Log.d("apiCallUrl", apiCallUrl);
        final HttpGet post = new HttpGet(apiCallUrl);

        post.setHeader("User-Agent", "DeliciousDroid");
        post.setHeader("Accept-Encoding", "gzip");

        DefaultHttpClient client = (DefaultHttpClient) HttpClientFactory.getThreadSafeClient();
        CredentialsProvider provider = client.getCredentialsProvider();
        Credentials credentials = new UsernamePasswordCredentials(username, authtoken);
        provider.setCredentials(SCOPE, credentials);

        client.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0);

        final HttpResponse resp = client.execute(post);

        final int statusCode = resp.getStatusLine().getStatusCode();

        if (statusCode == HttpStatus.SC_OK) {

            final HttpEntity entity = resp.getEntity();

            InputStream instream = entity.getContent();

            final Header encoding = entity.getContentEncoding();

            if (encoding != null && encoding.getValue().equalsIgnoreCase("gzip")) {
                instream = new GZIPInputStream(instream);
            }

            return instream;
        } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
            throw new AuthenticationException();
        } else {
            throw new IOException();
        }
    }

    /**
     * Converts an InputStream to a string.
     * 
     * @param is The InputStream to convert.
     * @return The String retrieved from the InputStream.
     */
    private static String convertStreamToString(InputStream is) {
        /*
         * To convert the InputStream to String we use the BufferedReader.readLine()
         * method. We iterate until the BufferedReader return null which means
         * there's no more data to read. Each line will appended to a StringBuilder
         * and returned as String.
         */
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

}