Java tutorial
/** * Copyright 2012 Scott Weeden-Moody * * Licensed 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. */ package com.lillicoder.newsblurry.net; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import junit.framework.Assert; import org.apache.http.client.CookieStore; import org.apache.http.cookie.Cookie; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Base64; import android.util.Log; /** * {@link CookieStore} implementation that uses {@link SharedPreferences} * as the persistent storage medium. Cookie names are used as the primary * ID for any given {@link Cookie}. * @author lillicoder */ public class PreferenceCookieStore implements CookieStore { private static final String TAG = PreferenceCookieStore.class.getSimpleName(); private static final String EXCEPTION_INVALID_CONTEXT_PARAM = "The given context must not be null."; private static final String WARNING_FAILED_TO_CLOSE_ARRAY_OUTPUT_STREAM = "Failed to close array output stream!"; private static final String WARNING_FAILED_TO_FIND_SERIALIZABLE_COOKIE_CLASS = "Failed to find SerializableCookie class when reading input stream, will return null."; private static final String WARNING_FAILED_TO_READ_COOKIE_FROM_STREAM = "Failed to read from input stream, will return null."; private static final String WARNING_FAILED_TO_WRITE_COOKIE_TO_STREAM = "Failed to write given cookie to output stream, cookie will not be persisted in preferences, will return null."; /** * Key for an individual stored cookie preference. */ private static final String PREFERENCE_COOKIE_ENTRY = "cookie_%s"; /** * Key for the stored cookie IDs preference. Each ID is the the cookie name * plus a hard-coded prefix. */ private static final String PREFERENCE_COOKIE_IDS = "storedCookieIds"; /** * Delimiter for cookie IDs stored in the cookie IDs preference. */ private static final String PREFERENCE_COOKIE_IDS_DELIMITER = ","; private Context _context; private Map<String, Cookie> _cache; /** * Creates a new instance of this store. All persisted * cookies in preferences will be loaded in-memory upon creation. * @param context {@link Context} to access {@link SharedPreferences} with. */ public PreferenceCookieStore(Context context) { if (context == null) throw new IllegalArgumentException(EXCEPTION_INVALID_CONTEXT_PARAM); this._context = context; this._cache = this.getPersistedCookies(); } @Override public void addCookie(Cookie cookie) { if (cookie != null) { if (cookie.isExpired(new Date())) this.deleteCookie(cookie); else this.saveCookie(cookie); } } @Override public void clear() { Map<String, Cookie> cache = this.getCookieCache(); // Clear saved cookies fist, then empty in-memory cache. SharedPreferences.Editor editor = this.getPreferences().edit(); for (Cookie cookie : cache.values()) { String cookieId = this.getCookiePreferenceEntryId(cookie); editor.remove(cookieId); } editor.remove(PREFERENCE_COOKIE_IDS); editor.commit(); // Clear cache. cache.clear(); } @Override public boolean clearExpired(Date date) { boolean hasClearedCookie = false; SharedPreferences.Editor editor = this.getPreferences().edit(); Map<String, Cookie> cache = this.getCookieCache(); for (Entry<String, Cookie> cachedCookie : cache.entrySet()) { String name = cachedCookie.getKey(); Cookie cookie = cachedCookie.getValue(); if (cookie.isExpired(new Date())) { // Clear from in-memory cache. cache.remove(name); // Clear from persistent storage. String cookieId = this.getCookiePreferenceEntryId(cookie); editor.remove(cookieId); hasClearedCookie = true; } } // Save changes for persistent storage. editor.commit(); // If we have cleared any cookie, we must update the // current cookie IDs in persistent storage. if (hasClearedCookie) this.updateCookieIds(cache.keySet()); return hasClearedCookie; } /** * Determines if this store contains a cookie with the given cookie name. * @param cookieName Name of the cookie to search for. * @return <code>true</code> if this store contains a cookie with the given name, * <code>false</code> otherwise. */ public boolean containsCookie(String cookieName) { Map<String, Cookie> cache = this.getCookieCache(); return cache.containsKey(cookieName); } @Override public List<Cookie> getCookies() { Map<String, Cookie> cache = this.getCookieCache(); return new ArrayList<Cookie>(cache.values()); } /** * Deletes the given {@link Cookie} from this store's * in-memory cache and persistent storage. If the cookie * is not currently stored, this method does nothing. * @param cookie {@link Cookie} to delete from cache and storage. */ private void deleteCookie(Cookie cookie) { Assert.assertTrue(cookie != null); Map<String, Cookie> cache = this.getCookieCache(); cache.remove(cookie.getName()); String cookieId = this.getCookiePreferenceEntryId(cookie); SharedPreferences.Editor editor = this.getPreferences().edit(); editor.remove(cookieId); editor.commit(); // We have removed a cookie, we must update the ID set with current valid cookie IDs. this.updateCookieIds(cache.keySet()); } /** * Decodes the given base-64 cookie string and returns a * {@link Cookie} representing that data. * @param encodedCookie Base-64 encoded string of a stored {@link Cookie}. * @return {@link Cookie} decoded from the given base-64 encoded cookie string. */ private Cookie decodeCookie(String encodedCookie) { Assert.assertTrue(encodedCookie != null); byte[] rawData = Base64.decode(encodedCookie, Base64.DEFAULT); ByteArrayInputStream in = new ByteArrayInputStream(rawData); Cookie cookie = null; try { ObjectInputStream inStream = new ObjectInputStream(in); SerializableCookie serializableCookie = (SerializableCookie) inStream.readObject(); if (serializableCookie != null) cookie = serializableCookie.getCookie(); } catch (ClassNotFoundException e) { Log.w(TAG, WARNING_FAILED_TO_FIND_SERIALIZABLE_COOKIE_CLASS); } catch (IOException e) { Log.w(TAG, WARNING_FAILED_TO_READ_COOKIE_FROM_STREAM); } return cookie; } /** * Encodes the given {@link SerializableCookie} as a base-64 encoded string. * This string is what will be persisted to preferences. * @param cookie {@link SerializableCookie} to encode. * @return Encoded base-64 string for the given cookie, * <code>null</code> if we failed to serialize the given cookie. */ private String encodeCookie(Cookie cookie) { Assert.assertTrue(cookie != null); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { ObjectOutputStream outStream = new ObjectOutputStream(out); SerializableCookie wrappedCookie = new SerializableCookie(cookie); outStream.writeObject(wrappedCookie); outStream.close(); } catch (IOException e) { Log.w(TAG, WARNING_FAILED_TO_WRITE_COOKIE_TO_STREAM); return null; } try { out.close(); } catch (IOException e) { Log.w(TAG, WARNING_FAILED_TO_CLOSE_ARRAY_OUTPUT_STREAM, e); } return Base64.encodeToString(out.toByteArray(), Base64.DEFAULT); } /** * Gets the {@link Context} for this cookie store. * @return {@link Context} for this cookie store. */ private Context getContext() { return this._context; } /** * Gets the in-memory cache of {@link Cookie} for this store. * @return In-memory {@link Map} of {@link Cookie} keyed by name. */ private Map<String, Cookie> getCookieCache() { return this._cache; } /** * Gets the cookie preference entry ID for the given {@link Cookie}. * @param cookie {@link Cookie} to get the preference ID of. * @return Cookie preference entry ID. */ private String getCookiePreferenceEntryId(Cookie cookie) { return String.format(PREFERENCE_COOKIE_ENTRY, cookie.getName()); } /** * Gets all persisted cookies stored in {@link SharedPreferences}. * @return {@link Map} of {@link Cookie} where each entry is keyed by name. */ private Map<String, Cookie> getPersistedCookies() { Map<String, Cookie> cookies = new ConcurrentHashMap<String, Cookie>(); SharedPreferences preferences = this.getPreferences(); String storedCookieIds = preferences.getString(PREFERENCE_COOKIE_IDS, null); if (storedCookieIds != null) { String[] cookieIds = storedCookieIds.split(PREFERENCE_COOKIE_IDS_DELIMITER); for (String id : cookieIds) { String cookieId = String.format(PREFERENCE_COOKIE_ENTRY, id); String encodedCookie = preferences.getString(cookieId, null); if (encodedCookie != null) { Cookie cookie = this.decodeCookie(encodedCookie); if (cookie != null) cookies.put(id, cookie); } } } return cookies; } /** * Gets an instance of {@link SharedPreferences} for * use in storing and retrieving cookies. * @return {@link SharedPreferences} instance for this store. */ private SharedPreferences getPreferences() { Context context = this.getContext(); return PreferenceManager.getDefaultSharedPreferences(context); } /** * Saves the given {@link Cookie} to this store's * in-memory cache and persistent storage. * @param cookie {@link Cookie} to save to cache and storage. */ private void saveCookie(Cookie cookie) { Assert.assertTrue(cookie != null); Map<String, Cookie> cache = this.getCookieCache(); cache.put(cookie.getName(), cookie); String cookieId = this.getCookiePreferenceEntryId(cookie); String encodedCookie = this.encodeCookie(cookie); SharedPreferences.Editor editor = this.getPreferences().edit(); editor.putString(cookieId, encodedCookie); editor.commit(); // We have added a cookie, we must update the ID set with the current valid cookie IDs. this.updateCookieIds(cache.keySet()); } /** * Updates this store's persistent index of cookie IDs to contain * the given collection of cookie IDs. */ private void updateCookieIds(Set<String> cookieIds) { SharedPreferences.Editor editor = this.getPreferences().edit(); String joinedCookieIds = TextUtils.join(PREFERENCE_COOKIE_IDS_DELIMITER, cookieIds); editor.putString(PREFERENCE_COOKIE_IDS, joinedCookieIds); editor.commit(); } }