|
|
|
|
File: [OW2-CVS] / sync4j / funambol / modules / foundation / connector / src / main / java / com / funambol / foundation / engine / source / Attic / PIMCalendarSyncSource.java
(download)
Revision: 1.18, Wed Nov 28 12:14:43 2007 UTC (2 years, 9 months ago) by nichele Branch: MAIN CVS Tags: r_funambol_6_5_12, r_funambol_6_5_11, r_foundation_6_5_9, r_foundation_6_5_10, HEAD Branch point for: b_v65 Changes since 1.17: +30 -13 lines Updated to AGPL v3. |
/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2005 - 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.foundation.engine.source;
import java.io.ByteArrayInputStream;
import java.sql.Timestamp;
import java.util.List;
import com.funambol.common.pim.calendar.Calendar;
import com.funambol.common.pim.converter.BaseConverter;
import com.funambol.common.pim.converter.CalendarToSIFE;
import com.funambol.common.pim.converter.TaskToSIFT;
import com.funambol.common.pim.converter.VCalendarConverter;
import com.funambol.common.pim.converter.VComponentWriter;
import com.funambol.common.pim.icalendar.ICalendarParser;
import com.funambol.common.pim.model.VCalendar;
import com.funambol.common.pim.sif.SIFCalendarParser;
import com.funambol.common.pim.xvcalendar.XVCalendarParser;
import com.funambol.foundation.exception.EntityException;
import com.funambol.foundation.items.manager.PIMCalendarManager;
import com.funambol.foundation.items.model.CalendarWrapper;
import com.funambol.foundation.util.Def;
import com.funambol.framework.engine.*;
import com.funambol.framework.engine.source.SyncContext;
import com.funambol.framework.engine.source.SyncSourceException;
import com.funambol.framework.tools.beans.BeanInitializationException;
/**
* This class is a PIMSyncSource for ICal and VCal data, which could include
* either an Event or a Todo. NB: The Todo part is not currenlty implemented.
*
* @version $Id: PIMCalendarSyncSource.java,v 1.18 2007/11/28 12:14:43 nichele Exp $
*/
public class PIMCalendarSyncSource extends PIMSyncSource {
//------------------------------------------------------------- Private data
private PIMCalendarManager manager;
//--------------------------------------------------------------- Properties
private Class entityType; // CalendarContent or one of its subclasses
public Class getEntityType() {
return entityType;
}
public void setEntityType(Class entityType) {
this.entityType = entityType;
}
//----------------------------------------------------------- Public methods
public void beginSync(SyncContext context) {
this.manager =
new PIMCalendarManager(JNDI_DATA_SOURCE_NAME,
context.getPrincipal().getUsername(), entityType);
super.manager = this.manager;
super.beginSync(context);
}
/**
* Makes an array of SyncItemKey objects representing the ID(s) of the
* twin(s) of a given calendar.
*
* @param syncItem the SyncItem representing the calendar whose twin(s) are
* looked for
* @throws SyncSourceException
* @return possibly, just one or no key should be in the array, but it can't
* be ruled out a priori that several keys get returned by this
* method
*/
public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem syncItem)
throws SyncSourceException {
try {
Calendar calendar = convert(getContentFromSyncItem(syncItem),
syncItem.getType());
List idList = null;
idList = manager.getTwins(calendar);
SyncItemKey[] keyList = new SyncItemKey[idList.size()];
for (int i = 0; i < idList.size(); i++) {
keyList[i] = new SyncItemKey((String)idList.get(i));
}
return keyList;
} catch(EntityException e) {
throw new SyncSourceException("Error retrieving twin item keys.", e);
}
}
/**
* Adds a SyncItem object (representing a calendar).
*
* @param syncItem the SyncItem representing the calendar
*
* @return a newly created syncItem based on the input object but with its
* status set at SyncItemState.NEW and the GUID retrieved by the
* back-end
*/
public SyncItem addSyncItem(SyncItem syncItem) throws SyncSourceException {
if (log.isTraceEnabled()) {
log.trace("PIMCalendarSyncSource addSyncItem begin");
}
Calendar c = null;
String content = null;
try {
content = getContentFromSyncItem(syncItem);
String contentType = syncItem.getType();
c = convert(content, contentType);
Timestamp ts = syncItem.getTimestamp();
// Adds the calendar, wraps it in sync information and uses it to
// create a new SyncItem which is the return value of this method
SyncItemImpl newSyncItem = new SyncItemImpl(
this , //syncSource
manager.addItem(c, ts), //key
null , //mappedKey
SyncItemState.NEW , //state
content.getBytes() , //content
null , //format
contentType , //type
ts //timestamp
);
return newSyncItem;
} catch (Exception e) {
log.error("SyncSource error adding a new synchronization item.");
throw new SyncSourceException("Error adding the item " + syncItem, e);
}
}
/**
* Updates a SyncItem object (representing a calendar).
*
* @param syncItem the SyncItem representing the calendar
*
* @return a newly created syncItem based on the input object but with its
* status set at SyncItemState.UPDATED and the GUID retrieved by the
* back-end
*/
public SyncItem updateSyncItem(SyncItem syncItem)
throws SyncSourceException {
if (log.isTraceEnabled()) {
log.trace("Updates a SyncItem from " + sourceURI);
}
Calendar c = null;
String content = null;
try {
String id = syncItem.getKey().getKeyAsString();
content = getContentFromSyncItem(syncItem);
String contentType = syncItem.getType();
c = convert(content, contentType);
// Modifies the calendar, wraps it in sync information and uses it to
// create a new SyncItem which is the return value of this method
SyncItemImpl newSyncItem = new SyncItemImpl(
this , //syncSource
manager.updateItem(id, c, syncItem.getTimestamp()), //key
null , //mappedKey
SyncItemState.UPDATED , //state
content.getBytes() , //content
null , //format
contentType , //type
null //timestamp
);
return newSyncItem;
} catch (Exception e) {
log.error("SyncSource error updating a synchronization item.", e);
throw new SyncSourceException("Error updating the item " + syncItem,
e);
}
}
/**
* Deletes the item with a given syncItemKey.
*
* @param syncItemKey
* @param timestamp in case of a soft deletion, this will be the registered
* moment of deletion; if a hard deletion is used, this
* field is irrelevant and may also be null
* @param softDelete it is true if the client requires a soft deletion
* @throws SyncSourceException
*/
public void removeSyncItem(SyncItemKey syncItemKey,
Timestamp timestamp ,
boolean softDelete )
throws SyncSourceException {
try {
if (!softDelete) {
if (log.isTraceEnabled()) {
log.trace("PIMCalendarSyncSource remove the SyncItem "
+ syncItemKey.getKeyAsString());
}
manager.removeItem(syncItemKey.getKeyAsString(), timestamp);
}
} catch (EntityException e) {
log.error("Sync source error: could not delete item with key"
+ syncItemKey, e);
throw new SyncSourceException("Error deleting item. ", e);
}
}
public SyncItem getSyncItemFromId(SyncItemKey syncItemKey)
throws SyncSourceException {
String id = null;
SyncItem syncItem = null;
id = syncItemKey.getKeyAsString();
if (log.isTraceEnabled()) {
log.trace("PIMCalendarSyncSource get SyncItem from " + id);
}
try {
CalendarWrapper cw;
try {
cw = manager.getItem(id);
} catch (Exception e) {
return null;
}
// Retrieves the calendar, wraps it in sync information and uses it
// to create a new SyncItem which is the return value of this method
syncItem = createSyncItem(id, cw.getCalendar(), SyncItemState.NEW);
} catch (EntityException e) {
throw new SyncSourceException("Error seeking SyncItem with ID: "
+ id, e);
}
return syncItem;
}
public boolean mergeSyncItems(SyncItemKey syncItemKey, SyncItem syncItem)
throws SyncSourceException {
try {
Calendar calendar =
convert(getContentFromSyncItem(syncItem), syncItem.getType());
boolean clientUpdateRequired =
manager.mergeItems(syncItemKey.getKeyAsString(),
calendar ,
syncItem.getTimestamp()
);
if (clientUpdateRequired) {
syncItem = getSyncItemFromId(syncItemKey);
}
return clientUpdateRequired;
} catch(EntityException e) {
log.error("SyncSource error: a merge did not succeed.", e);
throw new SyncSourceException("Error merging SyncItem with ID "
+ syncItemKey.getKeyAsString()
+ "with SyncItem " + syncItem,
e);
}
}
public void init() throws BeanInitializationException {
}
/**
* Makes an array of SyncItemKey objects representing all new calendar
* IDs, filtered according to a given time interval.
*
* @param since the earlier limit of the time interval
* @param to the later limit of the time interval
* @return a SyncItemKey array
*/
public SyncItemKey[] getNewSyncItemKeys(Timestamp since, Timestamp to)
throws SyncSourceException {
saveSyncTiming(since, to);
try {
List idList = manager.getNewItems(since, to);
return extractKeyArrayFromIdList(idList);
} catch(EntityException e) {
throw new SyncSourceException("Error retrieving new item keys.", e);
}
}
/**
* Makes an array of SyncItemKey objects representing all deleted calendar
* IDs, filtered according to a given time interval.
*
* @param since the earlier limit of the time interval
* @param to the later limit of the time interval
* @return a SyncItemKey array
*/
public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp since, Timestamp to)
throws SyncSourceException {
saveSyncTiming(since, to);
try {
List idList = manager.getUpdatedItems(since, to);
return extractKeyArrayFromIdList(idList);
} catch(EntityException e) {
throw new SyncSourceException("Error retrieving updated item keys.", e);
}
}
/**
* Makes an array of SyncItemKey objects representing all deleted calendar
* IDs, filtered according to a given time interval.
*
* @param since the earlier limit of the time interval
* @param to the later limit of the time interval
* @return a SyncItemKey array
*/
public SyncItemKey[] getDeletedSyncItemKeys(Timestamp since, Timestamp to)
throws SyncSourceException {
saveSyncTiming(since, to);
try {
List idList = manager.getDeletedItems(since, to);
return extractKeyArrayFromIdList(idList);
} catch(EntityException e) {
throw new SyncSourceException("Error retrieving deleted item keys.", e);
}
}
/**
* Makes an array of SyncItemKey objects representing all calendar IDs.
*
* @return a SyncItemKey array
*/
public SyncItemKey[] getAllSyncItemKeys() throws SyncSourceException {
try {
List idList = manager.getAllItems();
return extractKeyArrayFromIdList(idList);
} catch(EntityException e) {
throw new SyncSourceException("Error retrieving all item keys. ", e);
}
}
/**
* Gets the status of the SyncItem with the given key.
*
* @param syncItemKey as a SyncItemKey object
* @throws SyncSourceException
* @return the status as a char
*/
public char getSyncItemStateFromId(SyncItemKey syncItemKey)
throws SyncSourceException {
String id = "N/A"; // default value for error tracking
try {
// Slow sync
// @todo Implement, depending on a syncMode check
// Fast sync
id = syncItemKey.getKeyAsString();
if (log.isTraceEnabled()) {
log.trace("PIMCalendarSyncSource get SyncItem state from " + id);
}
char itemRawState = manager.getItemState(id, previousSyncTime);
if (itemRawState == Def.PIM_STATE_UNCHANGED) {
return SyncItemState.SYNCHRONIZED;
} else {
return itemRawState; // Def uses SyncItemState.* as constant
// values for N, D and U states
}
} catch (EntityException e) {
throw new SyncSourceException("Error getting the state of SyncItem "
+ "with ID " + id, e);
}
}
//---------------------------------------------------------- Private methods
/**
* Extracts the content from a syncItem.
*
* @param syncItem
* @return as a String object (same as
* PIMSyncSource#getContentFromSyncItem(String), but trimmed)
*/
protected String getContentFromSyncItem(SyncItem syncItem) {
String raw = super.getContentFromSyncItem(syncItem);
return raw.trim();
}
private Calendar webCalendar2Calendar(String text, String vCalType)
throws EntityException {
try {
ByteArrayInputStream buffer =
new ByteArrayInputStream(text.getBytes());
VCalendar vcalendar;
String version;
String charset;
if (log.isTraceEnabled()) {
StringBuilder sb = new StringBuilder(text.length() + 60);
sb.append("Converting: ").append(vCalType).append(" => Calendar ")
.append("\nINPUT = {").append(text).append('}');
log.trace(sb.toString());
}
if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
// vCalendar (1.0):
XVCalendarParser parser = new XVCalendarParser(buffer);
vcalendar = (VCalendar) parser.XVCalendar();
version = "1.0";
charset = BaseConverter.CHARSET_UTF7; // (versit spec)
} else {
// iCalendar (2.0):
ICalendarParser parser = new ICalendarParser(buffer);
vcalendar = (VCalendar) parser.ICalendar();
version = "2.0";
charset = BaseConverter.CHARSET_UTF8; // (RFC 2445)
}
if (deviceCharset != null) {
charset = deviceCharset; // overrides the default character set
}
String retrievedVersion = null;
if (vcalendar.getProperty("VERSION") != null) {
retrievedVersion = vcalendar.getProperty("VERSION").getValue();
}
vcalendar.addProperty("VERSION", version);
if (retrievedVersion == null) {
if (log.isTraceEnabled()) {
log.trace("No version property was found in the vCal/iCal "
+ "data: version set to " + version);
}
} else if (!retrievedVersion.equals(version)) {
if (log.isTraceEnabled()) {
log.trace("The version in the data was "
+ retrievedVersion
+ " but it's been changed to "
+ version);
}
}
VCalendarConverter vcf =
new VCalendarConverter(deviceTimeZone, charset);
Calendar c = vcf.vcalendar2calendar(vcalendar);
if (log.isTraceEnabled()) {
log.trace("Conversion done.");
}
return c;
} catch (Exception e) {
throw new EntityException("Error converting " + vCalType
+ " to Calendar. ", e);
}
}
private String calendar2webCalendar(Calendar calendar, String vCalType)
throws EntityException {
try{
String charset;
if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
// vCalendar (1.0):
charset = BaseConverter.CHARSET_UTF7; // (versit spec)
} else {
// iCalendar (2.0):
charset = BaseConverter.CHARSET_UTF8; // (RFC 2445)
}
if (deviceCharset != null) {
charset = deviceCharset; // overrides the default character set
}
VCalendarConverter vcf =
new VCalendarConverter(deviceTimeZone, charset);
VCalendar vcalendar;
String vcal;
if (log.isTraceEnabled()) {
log.trace("Converting: Calendar => " + vCalType);
}
if (vCalType.equals(PIMSyncSource.TYPE[VCAL])) { // VCAL
vcalendar =
vcf.calendar2vcalendar(calendar, true); // text/x-vcalendar
} else { // ICAL
vcalendar =
vcf.calendar2vcalendar(calendar, false); // text/calendar
}
VComponentWriter writer =
new VComponentWriter(VComponentWriter.NO_FOLDING);
vcal = writer.toString(vcalendar);
if (log.isTraceEnabled()) {
log.trace("OUTPUT = {" + vcal + "}. Conversion done.");
}
return vcal;
} catch (Exception e){
throw new EntityException("Error converting Calendar to "
+ vCalType, e);
}
}
/**
* Create a new SyncItem from a Calendar. The status is passed as
* an argument.
*
* @param calendar the Calendar object representing the input information
* @param status
* @throws EntityException if the content type is wrong or any problem
* occurs while creating a new SyncItem
* @return a newly created SyncItem object
*/
private SyncItem createSyncItem(String id, Calendar calendar, char status)
throws EntityException {
String contentType = getInfo().getPreferredType().getType();
if (log.isTraceEnabled()) {
StringBuilder sb = new StringBuilder(100);
sb.append("PIMCalendarSyncSource - creating item with:")
.append("\n> id: ").append(id)
.append("\n> status: ").append(status)
.append("\n> content-type: ").append(contentType);
log.trace(sb.toString());
}
SyncItem syncItem = null;
String stream = convert(calendar, contentType);
try {
syncItem = new SyncItemImpl(this, id, status);
} catch (Exception e) {
throw new EntityException(e);
}
syncItem.setType(contentType);
syncItem.setContent(stream.getBytes());
if (log.isTraceEnabled()) {
log.trace("PIMCalendarSyncSource created SyncItem");
}
return syncItem;
}
private SyncItemKey[] extractKeyArrayFromIdList(List idList) {
SyncItemKey[] keyList = new SyncItemKey[idList.size()];
for (int i = 0; i < idList.size(); i++) {
keyList[i] = new SyncItemKey((String) idList.get(i));
}
return keyList;
}
/**
* Converts a calendar in vCalendar/iCalendar, SIF-E or SIF-T format to a
* Calendar object.
*
* @param content as a String
* @param contentType
* @throws EntityException if the contentType is wrong or the conversion
* attempt doesn't succeed.
* @return a Calendar object
*/
private Calendar convert(String content, String contentType)
throws EntityException {
// Finds out which target type is required
for (int i = 0; i < TYPE.length; i++) {
if (contentType.equals(TYPE[i])) {
// Uses the proper converter method
switch(i) {
case VCAL:
case ICAL:
return webCalendar2Calendar(content, contentType);
case SIFE:
case SIFT:
return sif2Calendar(content, contentType);
default:
throw new EntityException("Can't make a Contact "
+ "out of a " + TYPE[i] + "!");
}
}
}
throw new EntityException("Content type unknown: " + contentType);
}
/**
* Converts a Calendar back to a streamable (vCalendar/iCalendar, SIF-E or
* SIF-T) format.
*
* @param calendar
* @param contentType
* @throws EntityException if the contentType is wrong or the conversion
* attempt doesn't succeed.
* @return the result in the required format
*/
private String convert(Calendar calendar, String contentType)
throws EntityException {
// Finds out which target type is required
for (int i = 0; i < TYPE.length; i++) {
if (contentType.equals(TYPE[i])) {
// Uses the proper converter method
switch(i) {
case VCAL:
case ICAL:
return calendar2webCalendar(calendar, contentType);
case SIFE:
case SIFT:
return calendar2sif(calendar, contentType);
default:
throw new EntityException("Can't make a " + TYPE[i]
+ "out of a Contact!");
}
}
}
throw new EntityException("Content type unknown: " + contentType);
}
private Calendar sif2Calendar(String xml, String sifType)
throws EntityException {
if (log.isTraceEnabled()) {
StringBuilder sb = new StringBuilder(xml.length() + 60);
sb.append("Converting: ").append(sifType).append(" => Calendar ")
.append("\nINPUT = {").append(xml).append('}');
log.trace(sb.toString());
}
ByteArrayInputStream buffer = null;
Calendar calendar = null;
try {
calendar = new Calendar();
buffer = new ByteArrayInputStream(xml.getBytes());
if ((xml.getBytes()).length > 0) {
SIFCalendarParser parser = new SIFCalendarParser(buffer);
calendar = parser.parse();
} else {
throw new EntityException("No data");
}
} catch (EntityException e) {
throw e;
} catch (Exception e){
throw new EntityException("Error converting " + sifType
+ " to Calendar. ", e);
}
if (log.isTraceEnabled()) {
log.trace("Conversion done.");
}
return calendar;
}
private String calendar2sif(Calendar calendar, String sifType)
throws EntityException {
if (log.isTraceEnabled()) {
log.trace("Converting: Calendar => " + sifType);
}
String xml = null;
BaseConverter c2xml;
Object thing;
try{
if (sifType.equals(PIMSyncSource.TYPE[SIFE])) { // SIF-E
c2xml = new CalendarToSIFE(deviceTimeZone, deviceCharset);
thing = calendar;
// NB: A CalendarToSIFE converts a Calendar into a SIF-E
} else { // SIF-T
c2xml = new TaskToSIFT(deviceTimeZone, deviceCharset);
thing = calendar.getTask();
// NB: A TaskToSIFT converts just a Task into a SIF-T
}
xml = c2xml.convert(thing);
if (log.isTraceEnabled()) {
log.trace("OUTPUT = {" + xml + "}. Conversion done.");
}
} catch (Exception e){
throw new EntityException("Error converting Calendar to " + sifType, e);
}
return xml;
}
}
| webmaster@ow2.org |
Powered by ViewCVS 0.9.4 |
Back to OW2 Forge