/*
* Copyright � 2008, 2010, Oracle and/or its affiliates. All rights reserved
*/
package com.sun.lwuit.browser;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
/**
* Handles saving of persistent data to the RMS.
* This includes cookies, form data, history (visited links) and images/documents cache.
*
* The Hashtable format used for the cookies and form data is a Hashtable with a String key (representing cookie's domain or form action URL).
* The value of the main Hastable is another Hashtable consisting of a String key (Cookie/Field name) and a String value (Cookie/Field value)
*
* For the history data the format is similar, but the value of the main hashtable is a vector holding strings of the visited links
*
* As for the cache, unlike all others the actual data is not saved in the memory 9due to its size).
* An index containing all relevant links is kept in the memory in a simple vector (which is serialized to a separate record store).
* The actual data is in a different record store where each record contains only the byte array representing the resource.
* The record number of the index record store and the data record store match.
*
* NOTE: This class is not a scalabale implementation and can have some problems with memory or RMS sizes.
* This is why the RMS can be disabled via the JAD properties - see RMS_ENABLED below.
*
* @author Ofir Leitner
*/
public class Storage {
/**
* Cookies
*/
public static final int TYPE_COOKIES = 0;
/**
* Form data
*/
public static final int TYPE_FORM_DATA = 1;
/**
* History
*/
public static final int TYPE_HISTORY = 2;
/**
* Cache
*/
public static final int TYPE_CACHE = 3;
static Vector cacheIndex;
static String CACHE_RMS_INDEX_NAME = "cacheidx";
static String CACHE_RMS_NAME = "cache";
/**
* The names of the record stores
*/
static String[] RMS_NAMES = {"cookies","formdata","history",CACHE_RMS_NAME};
/**
* Inidcating which data types to store to the RMS.
* This can be modified via the JAD property rms_* (where * is one of the RMS_NAMES)
*/
static boolean[] RMS_ENABLED = {true,true,true,true};
static Hashtable[] data = new Hashtable[3];
/**
* Returns a hashtable with all the cookies stored in the RMS
*
* @return a hashtable with all the cookies stored in the RMS
*/
public static Hashtable getCookies() {
return getRMSData(TYPE_COOKIES);
}
/**
* Returns a hashtable with all the form data stored in the RMS
*
* @return a hashtable with all the form data stored in the RMS
*/
public static Hashtable getFormData() {
return getRMSData(TYPE_FORM_DATA);
}
/**
* Commits the cookies into the RMS. Should be called when the midlet terminates.
*/
public static void commitCookies() {
commitDataToRMS(TYPE_COOKIES);
}
/**
* Commits the form data into the RMS. Should be called when the midlet terminates.
*/
public static void commitFormData() {
commitDataToRMS(TYPE_FORM_DATA);
}
/**
* Adds the given cookie to be stored in the RMS (Actual saving is done on commitCookies)
*
* @param domain The cookie's domain
* @param name The cookie's name
* @param value The cookie's value
*/
public static void addCookie(String domain,String name,String value) {
addDataRecord(TYPE_COOKIES, domain, name, value);
}
/**
* Adds the given form data record to be stored in the RMS (Actual saving is done on commitFormData)
*
* @param action The form's action URL
* @param id The field's name
* @param value The field's value
*/
public static void addFormData(String action,String id,String value) {
addDataRecord(TYPE_FORM_DATA, action, id, value);
}
/**
* Clears all persistent cookies data.
*/
public static void clearCookies() {
clear(TYPE_COOKIES);
}
/**
* Clears all persistent form data.
*/
public static void clearFormData() {
clear(TYPE_FORM_DATA);
}
/**
* Clears all persistent history data, and returns the new and empty Hashtable
* (Since unlike cookies and form data, the browser does not wokr on a copy, but on the actual Hashtable written to the RMS)
*/
public static Hashtable clearHistory() {
if (!RMS_ENABLED[TYPE_HISTORY]) {
return new Hashtable();
}
data[TYPE_HISTORY]=new Hashtable();
return data[TYPE_HISTORY];
}
/**
* Clears the files (html/images) cache on the RMS
*/
public static void clearCache() {
try {
RecordStore.deleteRecordStore(CACHE_RMS_NAME);
RecordStore.deleteRecordStore(CACHE_RMS_INDEX_NAME);
cacheIndex=null;
} catch (RecordStoreException ex) {
ex.printStackTrace();
}
}
/**
* Loads the index of the cache which includes all the saved resource names (Without their data)
*/
private static void loadCacheIndex() {
if (!RMS_ENABLED[TYPE_CACHE]) {
return;
}
try {
cacheIndex=new Vector();
RecordStore rms = RecordStore.openRecordStore(CACHE_RMS_INDEX_NAME, true);
int num=rms.getNumRecords();
for(int i=0;i<num;i++) {
byte[] buf=rms.getRecord(i+1);
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(buf));
String url=dis.readUTF();
cacheIndex.addElement(url);
}
rms.closeRecordStore();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns a resource (a stream to an image or HTML document) from the cache or null if none exists
*
* @param url The URL to search for
* @return a resource (a stream to an image or HTML document) from the cache or null if none exists
*/
public static ByteArrayInputStream getResourcefromCache(String url) {
if (!RMS_ENABLED[TYPE_CACHE]) {
return null;
}
if (cacheIndex==null) {
loadCacheIndex();
if (cacheIndex==null) { //couldn't open indexes
return null;
}
}
int index=cacheIndex.indexOf(url);
if (index==-1) { //Image not in cache
//System.out.println("Resource not found in cache - "+url);
return null;
}
try {
RecordStore rms = RecordStore.openRecordStore(CACHE_RMS_NAME, true);
byte[] buf=rms.getRecord(index+1);
rms.closeRecordStore();
//System.out.println("Resource found in cache "+url);
if (buf!=null) {
return new ByteArrayInputStream(buf);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Adds the given resource (image/HTML) into the cache
*
* @param url The URL this resource should be linked to (For future cache search)
* @param buf The resource buffer
* @param updateIfExists when true this will override current cache value for this url, when false if it exists the new value will be ignored
*/
public static void addResourceToCache(String url,byte[] buf,boolean updateIfExists) {
if (!RMS_ENABLED[TYPE_CACHE]) {
return;
}
if (cacheIndex==null) {
loadCacheIndex();
if (cacheIndex==null) { //couldn't open indexes
return;
}
}
int index=cacheIndex.indexOf(url);
if ((!updateIfExists) && (index!=-1)) {
//System.out.println("Image already exists - ignoring "+url);
return;
}
try {
RecordStore rms = RecordStore.openRecordStore(CACHE_RMS_NAME, true);
if (index==-1) {
rms.addRecord(buf, 0, buf.length);
} else {
rms.setRecord(index,buf, 0, buf.length);
}
rms.closeRecordStore();
cacheIndex.addElement(url);
rms = RecordStore.openRecordStore(CACHE_RMS_INDEX_NAME, true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF(url);
buf=baos.toByteArray();
rms.addRecord(buf, 0, buf.length);
rms.closeRecordStore();
//System.out.println("Image added to cache "+url);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns a Hashtable with the history data from the RMS.
* The returned Hashtable is the same table that is stored in the Storage class, and thus all elements added to that table will be automatically
* in the Storage hashtable and will be written on commitHistory
*
* @return A Hashtable with the history data
*/
public static Hashtable getHistory() {
if (!RMS_ENABLED[TYPE_HISTORY]) {
return new Hashtable();
}
try {
RecordStore rms = RecordStore.openRecordStore(RMS_NAMES[TYPE_HISTORY], true);
Hashtable history =new Hashtable();
int num=rms.getNumRecords();
for(int i=0;i<num;i++) {
byte[] buf=rms.getRecord(i+1);
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(buf));
String domain=dis.readUTF();
String url=dis.readUTF();
Vector urlData=(Vector)history.get(domain);
if (urlData==null) {
urlData=new Vector();
history.put(domain,urlData);
}
urlData.addElement(url);
}
rms.closeRecordStore();
data[TYPE_HISTORY]=new Hashtable();
return history;
} catch (Exception e) {
e.printStackTrace();
return new Hashtable();
}
}
public static void addHistory(String domain,String url) {
if (!RMS_ENABLED[TYPE_HISTORY]) {
return;
}
Vector urlData=(Vector)data[TYPE_HISTORY].get(domain);
if (urlData==null) {
urlData=new Vector();
data[TYPE_HISTORY].put(domain,urlData);
}
urlData.addElement(url);
}
/**
* Commits the history data to the RMS
*/
public static void commitHistory() {
if (!RMS_ENABLED[TYPE_HISTORY]) {
return;
}
if (data[TYPE_HISTORY]==null) {
//System.out.println("History RMS not opened");
return;
}
try {
//RecordStore.deleteRecordStore(RMS_NAMES[TYPE_HISTORY]);
RecordStore rms = RecordStore.openRecordStore(RMS_NAMES[TYPE_HISTORY], true);
for (Enumeration hosts=data[TYPE_HISTORY].keys();hosts.hasMoreElements();) {
String domain=(String)hosts.nextElement();
Vector urlData=(Vector)data[TYPE_HISTORY].get(domain);
if (urlData!=null) {
for (Enumeration e=urlData.elements();e.hasMoreElements();) {
String url = (String)e.nextElement();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF(domain);
dos.writeUTF(url);
byte[] buf=baos.toByteArray();
rms.addRecord(buf , 0, buf.length);
}
}
}
rms.closeRecordStore();
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Returns a Hashtable with the requested data from the RMS.
* This both creates a hashtable used in this class, and creates another copy of the data that is returned.
* The reason is that not all cookies should be saved (not all are persistent) so we have one copy for the session and another for saving.
*
* @param type One of TYPE_COOKIES or TYPE_FORM_DATA
* @return A Hashtable with the requested data
*/
private static Hashtable getRMSData(int type) {
if (!RMS_ENABLED[type]) {
return new Hashtable();
}
try {
RecordStore rms = RecordStore.openRecordStore(RMS_NAMES[type], true);
data[type]=new Hashtable();
Hashtable copy = new Hashtable();
int num=rms.getNumRecords();
for(int i=0;i<num;i++) {
byte[] buf=rms.getRecord(i+1);
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(buf));
String url=dis.readUTF();
String id=dis.readUTF();
String value=dis.readUTF();
Hashtable urlData=(Hashtable)data[type].get(url);
Hashtable urlDataCopy=(Hashtable)copy.get(url);
if (urlData==null) {
urlData=new Hashtable();
urlDataCopy=new Hashtable();
data[type].put(url,urlData);
copy.put(url,urlDataCopy);
}
urlData.put(id,value);
urlDataCopy.put(id,value);
}
rms.closeRecordStore();
return copy;
} catch (Exception e) {
e.printStackTrace();
return new Hashtable();
}
}
/**
* Adds the given record to be saved in the RMS. Note that saving is not done immediately but only when commitDataToRMS is called
*
* @param type One of TYPE_COOKIES or TYPE_FORM_DATA
* @param url The URL, either the domain of the cookie or the action URL of the form
* @param id The cookie name or field name
* @param value The cookie value or field value
*/
private static void addDataRecord(int type,String url,String id,String value) {
if (!RMS_ENABLED[type]) {
return;
}
Hashtable urlData=(Hashtable)data[type].get(url);
if (urlData==null) {
urlData=new Hashtable();
data[type].put(url,urlData);
}
urlData.put(id,value);
}
/**
* Commits the data (either cookies or form data) to the RMS. This should be called when the midlet termiantes.
*
* @param type One of TYPE_COOKIES or TYPE_FORM_DATA
*/
private static void commitDataToRMS(int type) {
if (!RMS_ENABLED[type]) {
return;
}
if (data[type]==null) {
//System.out.println("RMS was not opened");
return;
}
try {
RecordStore.deleteRecordStore(RMS_NAMES[type]);
RecordStore rms = RecordStore.openRecordStore(RMS_NAMES[type], true);
for (Enumeration hosts=data[type].keys();hosts.hasMoreElements();) {
String url=(String)hosts.nextElement();
Hashtable urlData=(Hashtable)data[type].get(url);
if (urlData!=null) {
for (Enumeration e=urlData.keys();e.hasMoreElements();) {
String id = (String)e.nextElement();
String value = (String)urlData.get(id);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF(url);
dos.writeUTF(id);
dos.writeUTF(value);
byte[] buf=baos.toByteArray();
rms.addRecord(buf , 0, buf.length);
}
}
}
rms.closeRecordStore();
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Clears the cookies/form data
*
* @param type One of TYPE_COOKIES or TYPE_FORM_DATA
*/
private static void clear(int type) {
if (!RMS_ENABLED[type]) {
return;
}
data[type]=new Hashtable();
}
}