/**
* Copyright (C) 2003-2005 Funambol
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package sync4j.syncclient.sps;
import java.util.Enumeration;
import java.util.Vector;
import javax.microedition.rms.RecordStore;
import javax.microedition.pim.Contact;
import javax.microedition.pim.ContactList;
import javax.microedition.pim.PIM;
import javax.microedition.pim.PIMList;
import javax.microedition.pim.PIMException;
import net.rim.device.api.system.PersistentObject;
import net.rim.device.api.system.PersistentStore;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.util.StringMatch;
import sync4j.syncclient.util.StaticDataHelper;
import sync4j.syncclient.blackberry.parser.ParserFactory;
import sync4j.syncclient.blackberry.parser.ContactParser;
import sync4j.syncclient.util.PagedVector;
/**
* This class provide methods
* to access device database and Contact List
*
* @author Fabio Maggi @ Funambol
* $Id: ContactDataStore.java,v 1.7 2005/11/28 14:50:06 fabius Exp $
*/
public class ContactDataStore extends DataStore {
//---------------------------------------------------------------- Constants
private static final String TIMESTAMP_RECORDSTORE = "TimeStampContactRS" ;
//------------------------------------------------------------- Private Data
//----------------------------------------------------------- Public methods
private static PersistentObject store;
private static Vector changes;
private PagedVector modifiedItems;
static {
store = PersistentStore.getPersistentObject(PERSISTENCE_KEY);
if(store.getContents() == null) {
store.setContents(new Vector());
store.commit();
}
changes = (Vector)store.getContents();
}
public ContactDataStore(int page) {
this();
setPage(page);
}
public ContactDataStore() {
super();
modifiedItems = null;
}
/**
* Set last timestamp in dedicate recordStore
* @param lastTimestamp
* @throws DataAccessException
**/
public void setLastTimestamp(long lastTimestamp)
throws DataAccessException {
RecordStore recordStore = null;
try {
recordStore = RecordStore.openRecordStore(TIMESTAMP_RECORDSTORE, true);
int numRecords = recordStore.getNumRecords();
String recordValue = String.valueOf(lastTimestamp);
if (numRecords == 1) {
recordStore.setRecord(1, recordValue.getBytes(), 0, (recordValue.getBytes()).length);
} else {
recordStore.addRecord(recordValue.getBytes(), 0, (recordValue.getBytes()).length);
}
} catch (Exception e) {
StaticDataHelper.log("error:" + e.getMessage());
throw new DataAccessException(e.getMessage());
} finally {
if (recordStore != null) {
try {
recordStore.closeRecordStore();
recordStore = null;
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
}
}
}
}
/**
* @return last timestamp from dedicate recordstore
* @throws DataAccessException
**/
public long getLastTimestamp()
throws DataAccessException {
RecordStore recordStore = null;
long lastTimestamp = 0;
try {
recordStore = RecordStore.openRecordStore(TIMESTAMP_RECORDSTORE, true);
int numRecords = recordStore.getNumRecords();
if (numRecords == 0) {
lastTimestamp = 0;
} else {
lastTimestamp = Long.parseLong(new String(recordStore.getRecord(1)));
}
} catch (Exception e) {
StaticDataHelper.log("error:" + e.getMessage());
throw new DataAccessException(e.getMessage());
} finally {
if (recordStore != null) {
try {
recordStore.closeRecordStore();
recordStore = null;
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
}
}
}
return lastTimestamp;
}
/**
* if record exist in database, update records
* if record not exist in database, add record
* @param record record to store
* @throws DataAccessException
**/
public Record setRecord(Record record, boolean modify)
throws DataAccessException{
try {
ContactList list = (ContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);
Contact contact = getContact(record.getKey(), list, modify);
if(contact == null) {
Dialog.inform("Contact is null.");
return null;
}
String content = fixTag(record.getUid());
ContactParser parser = ParserFactory.getParserInstance(list, contact, modify);
parser.parseContact(content);
contact.commit();
String uid = contact.getString(Contact.UID, 0);
record.setKey(uid);
list.close();
}catch(Exception e) {
e.printStackTrace();
throw new DataAccessException(e);
}
return record;
}
/**
* Obtains/Creates a Contact object depending on the modify option.
* @param String: key [contact UID]
* @param ContactList: contact list
* @param boolean: modify flag determines if the contact needs to be created/searched for from
* contact list the.
* @return Contact : returns a new contact object if modify flag was ser to false.else searches
* for contacts from the contact list for key matching existing UID and returns that.
* @throws Exception
*/
private Contact getContact(String key, ContactList list, boolean modify) throws Exception{
if(!modify) {
return list.createContact();
} else {
Enumeration enum = list.items();
while(enum.hasMoreElements()) {
Contact contact = (Contact)enum.nextElement();
if(contact.getString(Contact.UID, 0).equals(key)) {
return contact;
}
}
return null;
}
}
/**
* Sync4j escapes <. This method replaces.
* @param content
* @return
*/
private String fixTag(String content) {
StringBuffer contentBuffer = new StringBuffer(content);
StringMatch matcher = new StringMatch("<");
int matchOffset = matcher.indexOf(contentBuffer, 0);
while(matchOffset != -1){
contentBuffer.delete(matchOffset,matchOffset+4);
contentBuffer.insert(matchOffset,"<");
matchOffset = matcher.indexOf(contentBuffer, 0);
}
return contentBuffer.toString();
}
/**
* Delete a record from the contact database.
* @param record
* @throws DataAccessException
*/
public void deleteRecord(Record record) throws DataAccessException {
try {
ContactList list = (ContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);
Contact contact = getContact(record.getKey(), list, true);
if(contact != null)
list.removeContact(contact);
list.close();
}catch(Exception expn) {
throw new DataAccessException(expn);
}
}
/**
* Rules to add to the datastore
* a) If there is already record with state 'N' and 'U' is recieved don't update.
* b) If there is already record with state 'N' and 'D' is recieved remove the entry
* c) If there is a record with state 'U' and 'U' is got ignore it
* d) If there is a record with state 'U' and 'D' is got replace it with 'D'
*/
public void addRecord(String uid, char state, Contact contact) throws Exception{
String value = uid + "|" + state;
/**
* In case of deleted record, the information would not be available during sync.
* hence it is store along with the update information. In update/new case it is
* available in the contact db so it is not store to save space
*/
if(state == RECORD_STATE_DELETED) {
ContactList list = (ContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);
String data = getContactString(list, contact);
list.close();
value += "|" + data;
}
int size = changes.size();
String data = "";
boolean dataSet = false;
if(changes.contains(value))
return;
for(int i=size-1;i >= 0; --i) {
data = (String)changes.elementAt(i);
String dataUID = data.substring(0, data.indexOf("|"));
char dataState = data.substring(data.indexOf("|")+1).charAt(0);
if(!dataUID.equals(uid)) {
continue;
} else if(dataState == RECORD_STATE_NEW && state == RECORD_STATE_UPDATED) {
dataSet = true;
} else if (dataState == RECORD_STATE_NEW && state == RECORD_STATE_DELETED) {
changes.removeElement(data);
i = i-1;
dataSet = true;
}
else if(dataState == RECORD_STATE_UPDATED && state == RECORD_STATE_DELETED) {
changes.setElementAt(value,i);
changes.removeElement(data);
dataSet = true;
}
}
if(!dataSet) {
changes.addElement(value);
}
store.commit();
}
/**
* return no deleted records from device recordstore
*
* @return find records
* @throws DataAccessException
**/
public Vector getNoDeletedRecords()
throws DataAccessException {
return null;
}
/**
* return no deleted records from device recordstore
*
* @return find records
* @throws DataAccessException
**/
public boolean getNextRecords(Vector v)
throws DataAccessException {
boolean moreElements = true;
Contact contact = null;
Record record = null;
try {
for (int i=0; i<page; ++i) {
moreElements = items.hasMoreElements();
if (!moreElements) {
break;
}
contact = (Contact)items.nextElement();
record = new Record(contact.getString(contact.UID,0), RECORD_STATE_UNSIGNED, getContactString(pimList, contact));
v.addElement(record);
}
return moreElements;
} catch(Exception e) {
e.printStackTrace();
throw new DataAccessException(e);
}
}
/**
* return record from recordstore
* filter by record state
*
* @return find records
* @throws DataAccessException
**/
public boolean getNextRecords(Vector v, char state)
throws DataAccessException {
boolean moreElements = true;
if (modifiedItems == null) {
store = PersistentStore.getPersistentObject(PERSISTENCE_KEY);
if(store.getContents() == null) {
store.setContents(new Vector(MAX_ITEM_NUMBER));
store.commit();
}
modifiedItems = new PagedVector((Vector) store.getContents(), MAX_ITEM_NUMBER);
}
Vector ids = new Vector();
moreElements = modifiedItems.getNextPage(ids);
int size = ids.size();
String checkString = "|"+state;
Record rec = null;
for(int i=0;i < size; i++) {
String record = (String)ids.elementAt(i);
int checkStringIndex = record.indexOf(checkString);
if(checkStringIndex != -1) {
String uid = record.substring(0, checkStringIndex);
try {
if(state == RECORD_STATE_DELETED)
rec = new Record(record);
else
rec = new Record(uid,state, getContactAsString(uid));
v.addElement(rec);
} catch(DataAccessException e) {
e.printStackTrace();
throw new DataAccessException(e);
}
}
}
return moreElements;
}
/**
* returns a given contact as an xml string
* @param uid
* @return
* @throws DataAccessException
*/
private String getContactAsString(String uid) throws DataAccessException{
try {
String contanctAsString = null;
Contact contact = getContact(uid, (ContactList)pimList, true);
contanctAsString = getContactString((ContactList)pimList, contact);
return contanctAsString;
}catch(Exception e) {
e.printStackTrace();
throw new DataAccessException(e);
}
}
/**
* Converts one contact information from a black berry list to string format.
* @param String: uid [contact UID]
* @param ContactList: contact list
* @param Contact: contact.
* @return String : returns null id contact object is null else the method returns
* contact information parsed to string.
* @throws Exception,PIMException
*/
private String getContactString(PIMList list, Contact contact) throws Exception, PIMException {
if(contact == null) {
Dialog.inform("Contact is null.");
return null;
}
ContactParser parser = ParserFactory.getParserInstance((ContactList)list, contact, true);
String data = parser.toString(contact);
return data;
}
/**
* execute init recordstore operations
*/
public void startDSOperations() {
if (pimList != null) {
try {
pimList.close();
} catch (Exception e) {
StaticDataHelper.log("PIM database error: " + e.getMessage());
e.printStackTrace();
return;
}
items = null;
}
try {
pimList = PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);
items = pimList.items();
} catch (Exception e) {
StaticDataHelper.log("PIM database error: " + e.getMessage());
e.printStackTrace();
return;
}
}
/**
* execute commit recordstore operations
* remove records signed as DELETED 'D'
* mark UNSIGNED ' ' records signed as NEW 'N' and UPDATED 'U'
*
* @throws DataAccessException
*
*/
public void commitDSOperations()
throws DataAccessException {
changes.removeAllElements();
store.commit();
if (pimList != null) {
try {
pimList.close();
} catch (Exception e) {
//
// There is nothing we can do...
//
StaticDataHelper.log(e.getMessage());
}
items = null;
}
}
/**
* reset modifiedItems cursor
*/
public void resetModificationCursor() {
modifiedItems.reset();
}
public long getNextKey() {
//da implementare
return 0;
}
}