/**
* 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.Event;
import javax.microedition.pim.EventList;
import javax.microedition.pim.PIM;
import javax.microedition.pim.PIMException;
import net.rim.blackberry.api.pdap.BlackBerryEvent;
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.EventParser;
/**
* This class provide methods
* to access device database and Event List
*
* @author Fabio Maggi @ Funambol
* $Id: EventDataStore.java,v 1.8 2005/10/26 13:30:09 fabius Exp $
*/
public class EventDataStore extends DataStore {
//------------------------------------------------------------- Constants
protected static String TIMESTAMP_RECORDSTORE = "TimeStampEventRS" ;
protected static String EVENT_UID_RECORDSTORE = "EventsUIDRS" ;
protected static long PERSISTENCE_KEY = 0xafd852c254393341L ;
//------------------------------------------------------------- Private Data
//------------------------------------------------------------- Public methods
protected static PersistentObject store;
protected static Vector changes;
static {
store = PersistentStore.getPersistentObject(PERSISTENCE_KEY);
if(store.getContents() == null) {
store.setContents(new Vector());
store.commit();
}
changes = (Vector) store.getContents();
}
/**
* 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 {
EventList list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
Event event = getEvent(record.getKey(), list, modify);
if(event == null) {
Dialog.inform("Event is null.");
return null;
}
String content = fixTag(record.getUid());
EventParser parser = ParserFactory.getParserInstance(list, event, modify);
parser.parseEvent(content);
event.commit();
String uid = event.getString(Event.UID, 0);
record.setKey(uid);
list.close();
} catch(Exception e) {
e.printStackTrace();
throw new DataAccessException(e);
}
return record;
}
/**
* Obtains/Creates a Event object depending on the modify option.
* @param String: key [event UID]
* @param EventList: event list
* @param boolean: modify flag determines if the event needs to be created/searched for from
event list the.
* @return Event : returns a new event object if modify flag was ser to false.else searches
* for events from the event list for key matching existing UID and returns that.
* @throws Exception
*/
private Event getEvent(String key, EventList list, boolean modify)
throws Exception {
if(!modify) {
return list.createEvent();
}else {
Enumeration enum = list.items(key);
while(enum.hasMoreElements()) {
Event event = (Event)enum.nextElement();
if(event.getString(Event.UID, 0).equals(key))
return event;
}
return null;
}
}
/**
* Obtains/Creates a Event object depending on the modify option.
* @param String: key [event UID]
* @param EventList: event list
* @param boolean: modify flag determines if the event needs to be created/searched for from
event list the.
* @return Event : returns a new event object if modify flag was ser to false.else searches
* for events from the event list for key matching existing UID and returns that.
* @throws Exception
*/
private Event getEvent(String key, String hashKey, EventList list, boolean modify)
throws Exception {
if(!modify) {
return list.createEvent();
} else {
String hk = null;
Enumeration enum = list.items(key);
while(enum.hasMoreElements()) {
Event event = (Event)enum.nextElement();
hk = hashKey(list, event);
if(event.getString(Event.UID, 0).equals(key) && !hk.equals(hashKey)) {
return event;
}
}
return null;
}
}
private Event getNewEvent(Event event, String eventUid, String[] eventsUid) {
String eUid = null;
String value = null;
if (eventsUid == null) {
return event;
}
for (int i = 0, l = eventsUid.length; i < l; i++) {
eUid = eventsUid[i].substring(0, eventsUid[i].indexOf(","));
if (eUid.equals(eventUid)) {
return event;
}
}
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 event database.
* @param record
* @throws DataAccessException
*/
public void deleteRecord(Record record) throws DataAccessException {
try {
EventList list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
Event event = getEvent(record.getKey(), list, true);
if(event != null)
list.removeEvent(event);
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, Event event) 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 event db so it is not store to save space
*/
if(state == RECORD_STATE_DELETED) {
EventList list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
String data = getEventString(list, event);
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 {
Enumeration enum = null ;
EventList list = null ;
Vector noDeletedRecords = null ;
Event event = null ;
Record record = null ;
try {
noDeletedRecords = new Vector();
list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
enum = list.items();
while(enum.hasMoreElements()) {
event = (Event)enum.nextElement();
record = new Record(event.getString(event.UID,0), RECORD_STATE_UNSIGNED, getEventString(list, event));
noDeletedRecords.addElement(record);
}
list.close();
return noDeletedRecords;
} catch(Exception e) {
e.printStackTrace();
throw new DataAccessException(e);
}
}
/**
* return record from recordstore
* filter by record state
*
* @return find records
* @throws DataAccessException
**/
public Vector getRecords(char state)
throws DataAccessException {
int size ;
String[] eventsUid = null ;
String checkString = null ;
Vector changeVector = null ;
Vector detetedEvevent = null ;
Record rec = null ;
store = PersistentStore.getPersistentObject(PERSISTENCE_KEY);
if(store.getContents() == null) {
store.setContents(new Vector());
store.commit();
}
changes = (Vector) store.getContents() ;
size = changes.size() ;
checkString = "|"+state ;
changeVector = new Vector() ;
detetedEvevent = new Vector() ;
//
// - start code -
// this code is workaround
// blackberry bug in calendar event
//
if(state == RECORD_STATE_DELETED) {
try {
EventList list = null ;
String eventUid = null ;
Event event = null ;
list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
eventsUid = getEventsUid();
for (int i=0, l = eventsUid.length; i < l; i++) {
eventUid = eventsUid[i].substring(0, eventsUid[i].indexOf(","));
event = getEvent(eventUid, list, true);
if (event == null) {
rec = new Record(eventUid,state);
detetedEvevent.addElement(rec);
}
}
list.close();
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
}
return detetedEvevent;
}
//
// - end code -
// this code is workaround
// blackberry bug in calendar event
//
//
// - start code -
// this code is workaround
// blackberry bug in calendar event
//
if(state == RECORD_STATE_NEW) {
try {
EventList list = null ;
String eventUid = null ;
Event event = null ;
list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
eventsUid = getEventsUid();
Enumeration enum = list.items();
while(enum.hasMoreElements()) {
event = (Event) enum.nextElement();
eventUid = event.getString(Event.UID, 0);
event = getNewEvent(event, eventUid, eventsUid);
if (event == null) {
rec = new Record(eventUid ,state, getEventAsString(eventUid));
detetedEvevent.addElement(rec);
}
}
list.close();
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
}
return detetedEvevent;
}
//
// - start code -
// this code is workaround
// blackberry bug in calendar event
//
if(state == RECORD_STATE_UPDATED) {
try {
EventList list = null ;
String eventUid = null ;
Event event = null ;
String hashKey = null ;
list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
eventsUid = getEventsUid();
for (int i=0, l = eventsUid.length; i < l; i++) {
eventUid = eventsUid[i].substring(0, eventsUid[i].indexOf(","));
hashKey = eventsUid[i].substring(eventsUid[i].indexOf(",") + 1);
event = getEvent(eventUid, hashKey, list, true);
if (event != null) {
rec = new Record(eventUid ,state, getEventAsString(eventUid));
detetedEvevent.addElement(rec);
}
}
list.close();
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
}
return detetedEvevent;
}
//
// - end code -
// this code is workaround
// blackberry bug in calendar event
//
for(int i=0; i < size; i++) {
String record = (String)changes.elementAt(i);
int checkStringIndex = record.indexOf(checkString);
if(checkStringIndex != -1) {
String uid = record.substring(0, checkStringIndex);
try {
//
// this code is disable because
// blackberry bug in calendar event
//
/**
if(state == RECORD_STATE_DELETED)
rec = new Record(record);
else
rec = new Record(uid,state, getEventAsString(uid));
*/
rec = new Record(uid,state, getEventAsString(uid));
changeVector.addElement(rec);
} catch(DataAccessException e) {
e.printStackTrace();
throw new DataAccessException(e);
}
}
}
return changeVector;
}
/**
* returns a given event as an xml string
* @param uid
* @return
* @throws DataAccessException
*/
private String getEventAsString(String uid) throws DataAccessException{
try {
String contanctAsString = null;
EventList list = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
Event event = getEvent(uid, list, true);
contanctAsString = getEventString(list, event);
list.close();
return contanctAsString;
} catch(Exception e) {
e.printStackTrace();
throw new DataAccessException(e);
}
}
/**
* Converts one event information from a black berry list to string format.
* @param String: uid [event UID]
* @param EventList: event list
* @param Event: event.
* @return String : returns null id event object is null else the method returns
* event information parsed to string.
* @throws Exception,PIMException
*/
private String getEventString(EventList list, Event event) throws Exception, PIMException {
if(event == null) {
Dialog.inform("Event is null.");
return null;
}
EventParser parser = ParserFactory.getParserInstance(list, event, true);
String data = parser.toString(event);
return data;
}
/**
* execute init recordstore operations
*/
public void startDSOperations() {
}
/**
* 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();
setEventsUid();
}
public long getNextKey() {
//da implementare
return 0;
}
//
// - start code -
// this code is workaround
// blackberry bug in calendar event
//
/**
*
* set in RecordStore UID of event in BlackBerry Calendar
*
*/
private static void setEventsUid()
throws DataAccessException {
RecordStore recordStore = null ;
Enumeration eventsEnumeration = null ;
EventList list = null ;
Event event = null ;
String eventUid = null ;
String hashKey = null ;
try {
list = (EventList) PIM.getInstance().
openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
eventsEnumeration = list.items();
list.close();
//
// if RecordStore don't exist next line deleteRecordStore fails
//
recordStore = RecordStore.openRecordStore(EVENT_UID_RECORDSTORE, true);
recordStore.closeRecordStore();
RecordStore.deleteRecordStore(EVENT_UID_RECORDSTORE);
recordStore = RecordStore.openRecordStore(EVENT_UID_RECORDSTORE, true);
while(eventsEnumeration.hasMoreElements()) {
event = (Event)eventsEnumeration.nextElement() ;
eventUid = event.getString(event.UID, 0 );
hashKey = hashKey(list, event);
recordStore.addRecord((eventUid + "," + hashKey).getBytes() ,
0 ,
((eventUid + "," + hashKey).getBytes()).length) ;
}
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
} finally {
if (recordStore != null) {
try {
recordStore.closeRecordStore();
recordStore = null;
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
}
}
}
}
/**
*
* read RecordStore UID of Event in BlackBerry Calendar at last sync
*
*/
private static String[] getEventsUid()
throws DataAccessException {
String[] eventUids = null;
RecordStore recordStore = null;
try {
recordStore = RecordStore.openRecordStore(EVENT_UID_RECORDSTORE, true);
int numRecords = recordStore.getNumRecords();
eventUids = new String[numRecords];
for (int i= 1; i <= numRecords; i++) {
eventUids[i-1] = new String(recordStore.getRecord(i));
}
} 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 eventUids;
}
/**
* <description>
* @param list Pim List
* @param event
* @return hash key fields
*/
private static String hashKey (EventList list, Event event) {
long sd = 0 ;
long ed = 0 ;
int alarm = 0 ;
String allday = "";
String subject = "";
String location = "";
String note = "";
if (list.isSupportedField(event.START)) {
try {
sd = event.getDate(event.START, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field start date ");
}
}
if (list.isSupportedField(event.END)) {
try {
ed = event.getDate(event.END, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field end date");
}
}
if (list.isSupportedField(event.SUMMARY)) {
try {
subject = event.getString(event.SUMMARY, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field summary ");
}
}
if (list.isSupportedField(event.LOCATION)) {
try {
location = event.getString(event.LOCATION, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field location");
}
}
if (list.isSupportedField(event.NOTE)) {
try {
note = event.getString(event.NOTE, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field note");
}
}
if (list.isSupportedField(event.ALARM)) {
try {
alarm = event.getInt(event.ALARM, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field alarm");
}
}
if (list.isSupportedField(BlackBerryEvent.ALLDAY)) {
try {
boolean ad = ((BlackBerryEvent) event).getBoolean(BlackBerryEvent.ALLDAY, 0);
if (ad) {
allday = "1";
}
} catch (Exception e) {
StaticDataHelper.log("Not set field allday");
}
}
return Integer.toHexString(( String.valueOf(sd) +
String.valueOf(ed) +
String.valueOf(alarm) +
allday +
subject +
location +
note ).hashCode());
}
/**
* return no deleted records from device recordstore
*
* @return records found
*
* @throws DataAccessException
**/
public boolean getNextRecords(Vector v) throws DataAccessException {
throw new DataAccessException("To be implemented");
}
/**
* return record from recordstore
* filter by record state
*
* @return records found
*
* @throws DataAccessException
**/
public boolean getNextRecords(Vector v, char state) throws DataAccessException {
throw new DataAccessException("To be implemented");
}
/**
* reset modifiedItems cursor
*/
public void resetModificationCursor() {
}
}