Java tutorial
/* * 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(); } }