/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2003 - 2007 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* 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 Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*/
package com.funambol.syncclient.sps;
import java.util.Enumeration;
import java.util.Vector;
import javax.microedition.rms.RecordStore;
import net.rim.blackberry.api.pdap.BlackBerryContact;
//import javax.microedition.pim.Contact;
import net.rim.blackberry.api.pdap.BlackBerryContactList;
//import javax.microedition.pim.ContactList;
import javax.microedition.pim.PIM;
import javax.microedition.pim.PIMList;
import javax.microedition.pim.PIMException;
import com.funambol.syncclient.blackberry.parser.ContactParser;
import com.funambol.syncclient.blackberry.parser.ParserFactory;
import com.funambol.syncclient.util.PagedVector;
import com.funambol.syncclient.util.StaticDataHelper;
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;
/**
* This class provide methods
* to access device database and BlackBerryContact List
*
*/
public class ContactDataStore extends DataStore {
//---------------------------------------------------------------- Constants
private static final long CONTACT_KEY = 0x9995d95ae7690965L;
private static final String TIMESTAMP_RECORDSTORE = "TimeStampContactRS" ;
//------------------------------------------------------------- Private Data
//----------------------------------------------------------- Public methods
/**
* The persistent object of the device's store containing
* the Vector with the changes occurred on the device
*/
private static PersistentObject store;
private static Vector changes;
/**
* The Vector containing the changes occurred on the device.
* It is treated in predefined pages to avoid sending too many
* items to be modified to the server in the SyncML message
*/
private PagedVector modifiedItems;
static
{
store = PersistentStore.getPersistentObject(CONTACT_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 {
BlackBerryContactList list = (BlackBerryContactList)PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);
BlackBerryContact contact = getContact(record.getKey(), list, modify);
if (contact == null) {
Dialog.inform("BlackBerryContact 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(BlackBerryContact.UID, 0);
record.setKey(uid);
list.close();
}
catch (Exception e) {
System.out.println(">>> Improper data from server (vCard instead of SIF-C): " + e.getMessage());
//e.printStackTrace();
throw new DataAccessException(e.getMessage());
}
return record;
}
/**
* Obtains/Creates a BlackBerryContact object depending on the modify option.
* @param String: key [contact UID]
* @param BlackBerryContactList: contact list
* @param boolean: modify flag determines if the contact needs to be created/searched for from
* contact list the.
* @return BlackBerryContact : 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 BlackBerryContact getContact(String key, BlackBerryContactList list, boolean modify) throws Exception{
if(!modify) {
return (BlackBerryContact)list.createContact();//casting added
} else {
Enumeration enumeration = list.items();
while(enumeration.hasMoreElements()) {
BlackBerryContact contact = (BlackBerryContact)enumeration.nextElement();
if(contact.getString(BlackBerryContact.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 {
BlackBerryContactList list = (BlackBerryContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);
BlackBerryContact 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, BlackBerryContact 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) {
BlackBerryContactList list = (BlackBerryContactList) 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;
BlackBerryContact contact = null;
Record record = null;
try {
for (int i=0; i<page; ++i) {
moreElements = items.hasMoreElements();
if (!moreElements) {
break;
}
contact = (BlackBerryContact)items.nextElement();
record = new Record(contact.getString(contact.UID,0), RECORD_STATE_UNSIGNED, getContactString(pimList, contact));
v.addElement(record);
}
return moreElements;
} catch(Exception e) {
StaticDataHelper.log(">>> EXCEPTION in ContactDataStore.getNextRecords(Vector) --> " + e.toString());
e.printStackTrace();
throw new DataAccessException(e);
}
}
/**
* Prepares a vector of records representing the
* items modified on the device starting from
* the contacts in the device's store. Former
* these records were prepared sorted by modification
* state ('N', 'R', 'D'), but now they are added
* all together to the vector passed. The 'D'
* status is used to differentiate between Replaced
* and New status, to avoid to create a record
* consisting of the information payload too where
* you don't need such information to simply give
* the command to delete an item
*
* @param v The reference to a Vector to be modified
* by adding Record objects to it
*
* @param sep A separation character ('|') preceding
* the character representing the state
* of the modification ('N'ew, 'R'eplaced,
* 'D'eleted). Former the status character
* instead of the separator was passed, but
* now you need a placeholder to determine
* the exact position of the status character
* in the record string coming from the contacts
* store.
* Starting from the information contained in
* a {@code String} record in the contacts store,
* a new {@code Record} record for each item
* modified on the device is created
*
* @return {@code true} if {@code <Final/>} command from
* server not reached (there are more elements to be
* processed)
*
* @throws DataAccessException
*/
public boolean getNextRecords(Vector v, char sep) throws DataAccessException {
boolean moreElements = true;
if (modifiedItems == null) {
/*
* the persistent object of the device's store containing
* the Vector with the changes occurred on the device
*/
store = PersistentStore.getPersistentObject(CONTACT_KEY);
if (store.getContents() == null) {
store.setContents(new Vector(MAX_ITEM_NUMBER));
store.commit();
}
/*
* the Vector containing the changes occurred on the device.
* It is treated in predefined-size pages to avoid sending
* to the server in the SyncML message too many items to be
* modified
*/
modifiedItems = new PagedVector((Vector)store.getContents(), MAX_ITEM_NUMBER);
}
Vector ids = new Vector();
moreElements = modifiedItems.getNextPage(ids);
int size = ids.size();
Record rec = null;
for (int i = 0; i < size; i++) {
String record = (String)ids.elementAt(i);
//sep is always the '|' character passed to this method
int idx = record.indexOf(sep);
if (idx != -1) {
String uid = record.substring(0, idx);
/**
* The character ('N', 'D', 'R')
* immediately following the separator '|'
*/
char state = record.charAt(idx+1);
try {
if (state == RECORD_STATE_DELETED)
rec = new Record(record);
else
rec = new Record(uid, state, getContactAsString(uid));
v.addElement(rec);
}
catch (DataAccessException e) {
StaticDataHelper.log(">>> EXCEPTION in ContactDataStore.getNextRecords(Vector, char) --> " + e.toString());
throw new DataAccessException(e);
}
}
else {
// Should never happen
StaticDataHelper.log("Invalid record:" + record);
}
}
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;
BlackBerryContact contact = getContact(uid, (BlackBerryContactList)pimList, true);
contanctAsString = getContactString((BlackBerryContactList)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 BlackBerryContactList: contact list
* @param BlackBerryContact: 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, BlackBerryContact contact) throws Exception, PIMException {
if (contact == null) {
Dialog.inform("BlackBerryContact is null.");
return null;
}
ContactParser parser = ParserFactory.getParserInstance((BlackBerryContactList)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() {
//found comment: "da implementare"
return 0;
}
public void appendData(String data, long key) {
}
}