/*
* 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.spds;
import java.io.EOFException;
import java.io.IOException;
import javax.microedition.io.ConnectionNotFoundException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import com.funambol.syncclient.blackberry.SyncClient;
import com.funambol.syncclient.blackberry.email.impl.StringUtil;
import com.funambol.syncclient.blackberry.email.impl.Sync4j3DesCrypto;
import com.funambol.syncclient.blackberry.email.impl.Sync4jDesCrypto;
import com.funambol.syncclient.common.Base64;
import com.funambol.syncclient.common.StringTools;
import com.funambol.syncclient.spds.SyncException;
import com.funambol.syncclient.sps.DataAccessException;
import com.funambol.syncclient.sps.DataStore;
import com.funambol.syncclient.sps.Record;
import com.funambol.syncclient.util.PagedVector;
import com.funambol.syncclient.util.StaticDataHelper;
import net.rim.device.api.system.DeviceInfo;
/**
* The SyncManager and its implemntation is the contact point between
* a host application (client) and the synchronization engine (server).
* It is designed to hidden as much as possible the details of the
* synchronization logic, protocol, communication and so on
*
*/
public class SyncManagerImpl {
public static final int
ALERT_CODE_FAST = 200 ;
public static final int
ALERT_CODE_SLOW = 201 ;
public final static int
ALERT_CODE_ONE_WAY_FROM_SERVER = 204 ;
public static final int
ALERT_CODE_REFRESH_FROM_SERVER = 205 ;
public static final int
STATUS_CODE_OK = 200 ;
public static final int
STATUS_CODE_AUTHENTICATION_ACCEPTED = 212 ;
public static final int
STATUS_CODE_INVALID_CREDENTIALS = 401 ;
public static final int
STATUS_CODE_FORBIDDEN = 403 ;
public static final int
STATUS_CODE_NOT_FOUND = 404 ;
public static final int
STATUS_CODE_ERROR = 500 ;
public static final int
STATUS_CODE_PROCESSING_ERROR = 506 ;
public static final int
STATUS_REFRESH_REQUIRED = 508 ;
private static final String XML_ALERT = "/xml/alert.xml";
private static final String XML_INITIALIZATION = "/xml/init.xml" ;
private static final String XML_MODIFICATIONS = "/xml/mod.xml" ;
private static final String XML_MAPPING = "/xml/map.xml" ;
private static final char RECORD_STATE_NEW = 'N' ;
private static final char RECORD_STATE_DELETED = 'D' ;
private static final char RECORD_STATE_UPDATED = 'U' ;
private static final String TAG_ALERT = "Alert" ;
private static final String TAG_ADD = "Add" ;
private static final String TAG_CMD = "Cmd" ;
private static final String TAG_DATA = "Data" ;
private static final String TAG_DELETE = "Delete" ;
private static final String TAG_ITEM = "Item" ;
private static final String TAG_MSGID = "MsgID" ;
private static final String TAG_CMDID = "CmdID" ;
private static final String TAG_LOC_URI = "LocURI" ;
private static final String TAG_REPLACE = "Replace" ;
private static final String TAG_STATUS = "Status" ;
private static final String TAG_SYNC = "Sync" ;
private static final String TAG_SYNCBODY = "SyncBody" ;
private static final String TAG_SYNCHDR = "SyncHdr" ;
private static final String TAG_SYNCML = "SyncML" ;
private static final String TAG_TARGET = "Target" ;
private static final String TAG_FORMAT = "Format" ;
/**
* Specifies the maximum byte size of any
* response message to a given SyncML request
* [SyncML Meta Information 1.2, 09-05-2005, � 5.2.9]
*/
private static final String MAX_MSG_SIZE = "16000";// FIXME: use the configuration
private static final String DEVICE_ID_PREFIX = "fbb-";
//---------------------------------------------------------------- Private data
private Hashtable serverAlerts = null ;//instantiated in checkServerAlerts(String)
private String alertXML = null ;
private String clientInitXML = null ;
private String modificationsXML = null ;
private String mappingXML = null ;
private String modificationsXMLTemplate = null;
private String serverUrl = null ;
private String serverUrlStatus = null ;
private String gatewayApn = null ;
private String gatewayIp = null ;
private String gatewayPort = null ;
private String gatewayUrl = null ;
private String login = null ;
private String sourceUri = null ;
private String sourceType = null ;
private String sourceName = null ;
private String sessionID = null ;
private Hashtable mappings = null ;
private DataStore dataStore = null ;
private long lastTimestamp ;
private long nextTimestamp ;
private boolean moreRecordsDelete = true ;
private boolean moreRecordsNew = true ;
private boolean moreRecordsUpdate = true ;
private String alertCode = null ;
private String encryptType = null ;
Runtime rt = null ;
private Sync4jDesCrypto desCrypto = null ;
private Sync4j3DesCrypto sync3desCrypto = null ;
private StaticDataHelper sdh = null ;
private String deviceId = null ;
/**
* This member stores the Status tags to send back to the server
* in the next message. It is modified at each item received,
* and is cleared after the status are sent.
*/
private Vector statusCommands = null ;
/**
* This member is used to store the current message ID.
* It is sent to the server in the MsgID tag.
*/
private int msgID = 0;
/**
* To be used in static context in
* SyncMLClientImpl: this is only
* another reference to the non-static
* field {@code sessionID}
*/
protected static String session_id;
/**
* The connection counter used in
* {@link SyncMLClientImpl#sendMessage()}
* to take trace of the number of
* open connections to control the
* origin of a {@link java.io.IOException}
* ("Max connections opened")
*/
protected static int c;
/*
* The static field too needs
* to be initialized!
*/
static {
c = 0;
}
//------------------------------------------------------------- Constructors
/**
* Creates a SyncManagerImpl.
* The datastore is identified by the dataStoreName parameter
*
* @param dataStoreName The dataStore to Sync
*
*/
protected SyncManagerImpl (String dataStoreName ,
String sourceType ,
String sourceName) {
this.sourceUri = dataStoreName;
this.sourceType = sourceType;
this.sourceName = sourceName;
this.mappings = new Hashtable();
this.dataStore = DataStore.getDataStore(dataStoreName);
sdh = new StaticDataHelper();
statusCommands = new Vector();
}
//----------------------------------------------------------- Public methods
public void setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
}
public void setLogin(String login) {
this.login = login;
}
public void setGatewayApn(String gatewayApn) {
this.gatewayApn = gatewayApn;
}
public void setGatewayIp(String gatewayIp) {
this.gatewayIp = gatewayIp;
}
public void setGatewayPort(String gatewayPort) {
this.gatewayPort = gatewayPort;
}
public void setSessionId(String sessionId) {
this.sessionID = sessionId;
}
public void setAlertCode(String alertCode) {
this.alertCode = alertCode;
}
/**
* Synchronizes synchronization sources
*
* @throws SyncException If an error occurs during synchronization
* @throws AuthenticationException If the server responded with "non authorized" return code
*/
public void sync() throws SyncException,
AuthenticationException,
UpdateException,
Exception {
sdh.log("[LOG]Entering sync() in SyncManagerImpl: 'inSynchronize' is " + SyncClient.inSynchronize);
/*
* the connection counter has to
* be set to 0 at begin of each
* session
*/
SyncManagerImpl.c = 0;
// Restart the message counter.
resetMsgId();
deviceId = DEVICE_ID_PREFIX + String.valueOf(DeviceInfo.getDeviceId());
String response = "";
if (gatewayApn != null && gatewayApn.length() > 0) {
gatewayUrl = ";WAPGatewayIP=" +
gatewayIp +
";WAPGatewayAPN=" +
gatewayApn +
";WAPGatewayPort=" +
gatewayPort ;
} else {
gatewayUrl = "";
}
if (!DeviceInfo.isSimulator()) {
serverUrl = serverUrl + gatewayUrl;
}
try {
lastTimestamp = dataStore.getLastTimestamp();
} catch (DataAccessException e) {
throw new SyncException(e.getMessage());
}
nextTimestamp = System.currentTimeMillis();
try {
loadResources();
sdh.loadLanguage(this.getClass());
sdh.loadDefaultValue();
} catch (IOException e) {
throw new UpdateException( "Error: ("
+ e.getClass().getName()
+ "): "
+ e.getMessage()
);
}
if (sessionID == null) {
sessionID = String.valueOf(System.currentTimeMillis());
//to be used in static context in SyncMLClientImpl
session_id = sessionID;
}
encryptType = sdh.getEncryptDefault();
prepareInizializationMessage();
StaticDataHelper.log("\n\n\n");
StaticDataHelper.log("sending:\n" + clientInitXML);
response = syncInitialization();
StaticDataHelper.log(response);
checkServerAlerts(response);
// start debug
StaticDataHelper.log("\n\n\n");
String keyStr = "";
Enumeration keys = serverAlerts.keys();
while (keys.hasMoreElements()) {
keyStr = (String)keys.nextElement();
StaticDataHelper.log("source - Name: " + keyStr + " | Status: " + serverAlerts.get(keyStr));
}
dataStore.setAlertCode((String)serverAlerts.get(keyStr));
// end debug
StaticDataHelper.log("\n\n\n");
StaticDataHelper.log("receiving\n:" + response);
serverUrlStatus = serverUrl;
serverUrl = response.substring(response.indexOf("<RespURI>") + 9,
response.indexOf("</RespURI>"));
if (!DeviceInfo.isSimulator()) {
serverUrl += gatewayUrl;
}
//
// execute init dataStore operation
//
dataStore.startDSOperations();
//
// Notifies the sources that the synchronization is going to begin
//
boolean sendingClientModifications = true ;
boolean done = false;
// ================================================================
/*
* the implementation of the client/server multi-messaging
* through a do while loop
*/
do {
int alertCode = -1;
response = null;
// --------------------------------------------------------
sendingClientModifications =
prepareModificationMessage(sendingClientModifications);
// --------------------------------------------------------
StaticDataHelper.log("\n\n\n\n\n\n\n");
StaticDataHelper.log("sending\n:" + modificationsXML);
response = syncModifications();
StaticDataHelper.log("\n\n\n\n\n\n\n");
StaticDataHelper.log("receiving\n:" + response);
processModifications(response);
done = ((response.indexOf("<Final/>") >= 0) ||
(response.indexOf("</Final>") >= 0));
}
while (!done);
// ================================================================
//
// We still have to send the latest mappings
//
prepareMappingMessage();
StaticDataHelper.log("\n\n\n\n\n\n\n");
StaticDataHelper.log("sending\n:" + mappingXML);
response = syncMapping();
StaticDataHelper.log("\n\n\n\n\n\n\n");
StaticDataHelper.log("receiving\n:" + response);
//
// Set the last anchor to the next timestamp for all the sources that
// has been synchronized
//
try {
dataStore.commitDSOperations();
dataStore.setLastTimestamp(nextTimestamp);
} catch (DataAccessException e) {
throw new SyncException(e.getMessage());
}
}
//---------------------------------------------------------- Private methods
/**
* Sync initialization
*
* @throws SyncException, AuthenticationException, UpdateException
* @return sync initialization response
*/
private String syncInitialization() throws SyncException,
AuthenticationException,
UpdateException {
sdh.log("[LOG]Entering syncInitialization() in SyncManagerImpl: 'inSynchronize' is " + SyncClient.inSynchronize);
String response = "";
StaticDataHelper.log("Sync Initialization...");
try {
response = postRequest(clientInitXML);
}
catch (SyncException e) {
String msg = "[LOG]Error in Sync Initialization. SyncException in SyncManagerImpl.postRequest(:String): " + e.toString();
StaticDataHelper.log(msg);
throw e;
}
catch (Exception e) {
String msg = e.getMessage();
StaticDataHelper.log("[LOG]Error in Sync Initialization. Not specified Exception in SyncManagerImpl.postRequest(:String): " + e.toString());
throw new SyncException(msg);
}
checkStatus(response, TAG_SYNCHDR );
checkStatus(response, TAG_ALERT );
StaticDataHelper.log("Initialization done!");
return response;
}
/**
* Synch Modification.
*
* @throws UpdateException
*
* @return sync modification response
*/
private String syncModifications() throws SyncException {
String response = null;
sdh.log("[LOG]Entering syncModifications() in SyncManagerImpl: 'inSynchronize' is " + SyncClient.inSynchronize);
StaticDataHelper.log("Starting syncModifications()...");
try {
response = postRequest(modificationsXML);
}
catch(SyncException e) {
String msg = e.getMessage();
StaticDataHelper.log("Error in SincManagerImpl.syncModifications(): "
+ msg);
throw new SyncException(msg);
}
catch (Exception e) {
String msg = e.getMessage();
StaticDataHelper.log("Error in SincManagerImpl.syncModifications(): "
+ msg);
throw new SyncException(msg);
}
StaticDataHelper.log("Modifications done!");
return response;
}
/**
* Send alert message.
*
* FIXME: this method is never used [25.04.2006]
*
* @throws UpdateException
* @return sync modification response
*/
private String sendAlertMessage()
throws SyncException {
String response = null;
StaticDataHelper.log("Sync Modifications...");
try {
response = postRequest(alertXML);
} catch(SyncException e) {
String msg = "Error Modifications: " + e.getMessage();
throw new SyncException(msg);
} catch (Exception e) {
String msg = "Error Modifications: " + e.getMessage();
StaticDataHelper.log(msg);
throw new SyncException(msg);
}
StaticDataHelper.log("Modification done!");
return response;
}
/**
* Sync LUID-GUID mapping.
*
* @throws UpdateException
* @return sync modification response
*/
private String syncMapping() throws SyncException {
sdh.log("[LOG]Entering syncMapping() in SyncManagerImpl: 'inSynchronize' is " + SyncClient.inSynchronize);
String response = null;
StaticDataHelper.log("Sending mapping...");
try {
return postRequest(mappingXML);
} catch (Exception e) {
String msg = "Error Sync Mapping: " + e.getMessage();
StaticDataHelper.log(msg);
throw new SyncException(msg);
}
}
/**
* Posts the given message to the url specified by <i>url</i>.
*
* @param request the request msg
* @return the url content as a byte array
* @throws SyncException in case of network errors: e.toString() is
* the message of the exception rethrown from
* SyncMLClientImpl.sendMessage()
*/
private String postRequest(String request) throws SyncException {
try {
SyncMLClientImpl syncMLClient = new SyncMLClientImpl(serverUrl);
return syncMLClient.sendMessage(request);
}
catch (NetworkException e) {
String msg = "Synchronization failed in SyncManagerImpl.postRequest(request): no network available --> ";
StaticDataHelper.log( msg + e.toString() );
throw new SyncException(e.getMessage());
}
catch (ConnectionNotFoundException e) {
//e.g. missing "http://" in the Server URI (Admin Tool) like "localhost:8080/funambol/ds"
//instead of "http://localhost:8080/funambol/ds"
//causes the system to search for the not existing class
//"net.rim.device.cldc.io.localhost.Protocol"
String msg = "Synchronization failed in SyncManagerImpl.postRequest(request): protocol not found --> ";
StaticDataHelper.log( msg + e.toString() );
throw new SyncException(e.getMessage());
} catch (EOFException e) {
String msg = "Synchronization failed in SyncManagerImpl.postRequest(request): connection broken because of end of stream --> ";
StaticDataHelper.log( msg + e.toString() );
if (null != e.getMessage()) {
throw new SyncException(e.getMessage());
} else {//normally this exception has no message, but 'null'
throw new SyncException("End of stream has been reached unexpectedly during input");
}
} catch (IOException e) {
String msg = "Synchronization failed in SyncManagerImpl.postRequest(request): connection broken --> ";
StaticDataHelper.log( msg + e.toString() );
throw new SyncException(e.getMessage() != null ? e.getMessage() : e.toString());//see SyncClient::getSynchError() the last 'else' clause
} catch (Exception e) {
String msg = "Error sending the request in SyncManagerImpl.postRequest(request): network error --> ";
StaticDataHelper.log( msg + e.toString() );
throw new SyncException(e.getMessage());
}
}
/**
* Checks if the given response message is authenticated by the server
*
* @param msg the message to be checked
* @param statusOf the command of which we want to check the status
*
* @throws AuthenticationException in case the request has not been
* authenticated by the server
* @throws SyncException in case of other errors
*/
private void checkStatus(String msg, String statusOf)
throws SyncException, AuthenticationException, UpdateException {
Vector xmlMsg = new Vector();
xmlMsg.addElement(msg);
Vector statusTag = getXMLTag(getXMLTag(getXMLTag(xmlMsg, TAG_SYNCML),TAG_SYNCBODY), TAG_STATUS);
int l = statusTag.size();
for (int i=0; i < l; i++) {
if (getXMLTagValue((String) statusTag.elementAt(i), TAG_CMD).equals(statusOf)) {
String statusCode = getXMLTagValue((String) statusTag.elementAt(i), TAG_DATA);
if (String.valueOf(STATUS_CODE_OK).equals(statusCode)) {
//
// 200
//
return;
}
else if (String.valueOf(STATUS_REFRESH_REQUIRED).equals(statusCode)) {
//
// 508
//
return;
}
else if (String.valueOf(STATUS_CODE_AUTHENTICATION_ACCEPTED).equals(statusCode)) {
//
// 212
//
return;
}
else if (String.valueOf(STATUS_CODE_INVALID_CREDENTIALS).equals(statusCode)) {
//
// 401
//
throw new AuthenticationException("Sorry, you are not authorized to synchronize \""
+ sourceUri
+ "\". Please check your username and password in the Funambol configuration panel.");
}
else if (String.valueOf(STATUS_CODE_FORBIDDEN).equals(statusCode) ||
String.valueOf(STATUS_CODE_PROCESSING_ERROR).equals(statusCode)) {
//
// 403 or 506 (but 506 can be a more generic server error)
//
throw new AuthenticationException(
"Sorry, you are not authorized to synchronize \""
+ sourceUri
+ "\". Go to the preferences panel and make sure your account settings are correct."
);
}
else if (String.valueOf(STATUS_CODE_NOT_FOUND).equals(statusCode)) {
//
// 404
//
throw new AuthenticationException(
"Sorry, you can't synchronize \""
+ sourceUri
+ "\", because the name of the Remote Mail in the Funambol configuration panel mismatches the corresponding name on the server."
);
}
else {
//
// Unhandled status code
//
try {
dataStore.setLastTimestamp(0l);
} catch (DataAccessException e) {
throw new SyncException(e.getMessage());
}
throw new UpdateException(statusCode);
}
}
} // next i
String msgE = msg;
StaticDataHelper.log(msgE);
throw new SyncException(msgE);
}
/**
* <p>Checks response status for the synchronized databases and saves their
* serverAlerts
* <p>If this is the first sync for the source, the status code might change
* according to the value of the PARAM_FIRST_TIME_SYNC_MODE configuration
* property
* <p>If firstTimeSyncMode is not set, the alert is left unchanged. If it is
* set to a value, the specified value is used instead
*
* @param msg The message to be checked
*
* @throws AuthenticationException In case the request has not been
* authenticated by the server
* @throws SyncException In case of other errors
**/
private void checkServerAlerts(String msg) throws SyncException,
AuthenticationException {
Vector xmlMsg = null;
Vector itemMsg = null;
Vector targetTag = null;
String alert = null;
String item = null;
String dataStore = null;
xmlMsg = new Vector();
xmlMsg.addElement(msg);
serverAlerts = new Hashtable();
Vector alertTag = getXMLTag(getXMLTag(getXMLTag(xmlMsg, TAG_SYNCML),TAG_SYNCBODY), TAG_ALERT);
int l = alertTag.size();
for (int i=0; i < l; i++) {
itemMsg = new Vector();
alert = getXMLTagValue((String) alertTag.elementAt(i), TAG_DATA);
item = getXMLTagValue((String) alertTag.elementAt(i), TAG_ITEM);
itemMsg.addElement(item);
targetTag = getXMLTag(itemMsg, TAG_TARGET);
int m = targetTag.size();
for (int j=0; j < m; j++) {
dataStore = getXMLTagValue((String) targetTag.elementAt(j), TAG_LOC_URI);
}
StaticDataHelper.log("The server alert code for " + dataStore + " is " + alert);
serverAlerts.put(dataStore, alert);
}
}
/**
* Prepares inizialization SyncML message
*
*/
private void prepareInizializationMessage() throws SyncException {
sdh.log("[LOG]Entering prepareInizializationMessage() in SyncManagerImpl: 'inSynchronize' is " + SyncClient.inSynchronize);
String dbAlertsXML = null;
dbAlertsXML = createAlerts(sourceUri);
//
// authentication basic
//
if (encryptType != null && !encryptType.equals("none")) {
if (encryptType.equals("des")) {
desCrypto = new Sync4jDesCrypto(Base64.encode(login.getBytes()));
}
else if (encryptType.equals("3des")) {
sync3desCrypto = new Sync4j3DesCrypto(Base64.encode(login.getBytes()));
}
}
login = new String(Base64.encode(login.getBytes()));
clientInitXML = messageFormat(clientInitXML, "{0}", sessionID );
clientInitXML = messageFormat(clientInitXML, "{10}", getNextMsgId() );
clientInitXML = messageFormat(clientInitXML, "{1}", serverUrl );
clientInitXML = messageFormat(clientInitXML, "{2}", login );
clientInitXML = messageFormat(clientInitXML, "{3}", MAX_MSG_SIZE );
clientInitXML = messageFormat(clientInitXML, "{4}", dbAlertsXML );
clientInitXML = messageFormat(clientInitXML, "{5}", deviceId );
login= new String(Base64.decode(login.getBytes()));
}
/*
* Add a status command to the statusCommand vector
*
* @param cmdTag should be TAG_ADD or TAG_REPLACE
* @param msgRef the MsgId received from the server
* @param cmd the command processed by processModificationCommand
*
* @return none
*/
private void addStatusCommand(String cmdTag, String msgRef, Hashtable cmd) {
StringBuffer status = new StringBuffer();
// Build the status command
try {
status.append("<MsgRef>" + msgRef + "</MsgRef>\n");
status.append("<CmdRef>" + cmd.get(TAG_CMDID) + "</CmdRef>\n");
status.append("<Cmd>" + cmdTag + "</Cmd>\n");
if( TAG_ADD.equals(cmdTag) ) {
status.append("<SourceRef>" + cmd.get(TAG_LOC_URI) + "</SourceRef>\n");
}
else {
status.append("<TargetRef>" + cmd.get(TAG_LOC_URI) + "</TargetRef>\n");
}
if(cmd.get(TAG_STATUS) == null){
// Should not happen, but if for some error
// this function is called without the status
// set it to error
status.append("<Data>" + STATUS_CODE_ERROR + "</Data>\n");
}
else {
status.append("<Data>" + cmd.get(TAG_STATUS) + "</Data>\n");
}
}
catch (Exception e){
sdh.log("[addStatusCommand] Error setting the status command " +
"(msgRef=" + msgRef +")");
}
// Save the status command
statusCommands.addElement(status.toString());
}
/*
* Processes a modification command received from server,
* returning the command parts in an Hashtable
*
* @param command The modification command from server
* @return Hashtable the content of the command, with a
* TAG_STATUS set in case of error
* @throws SyncException if the command parsing failed
*
*/
private Hashtable processModificationCommand(String command)
throws SyncException {
// FIXME: the command can be one, with many items....
String key = null;
String content = null;
String encryptInfo = null;
String cmdId = null;
Hashtable cmd = new Hashtable();
// Get message Id
try {
cmdId = getXMLTagValue(command, TAG_CMDID);
cmd.put(TAG_CMDID, cmdId);
}
catch (StringIndexOutOfBoundsException e){
sdh.log("[processModificationCommand]" +
"Invalid command Id from server: " + command);
throw new SyncException("Invalid command from server.");
}
// Get item key
try {
key = getXMLTagValue(command, TAG_LOC_URI);
cmd.put(TAG_LOC_URI, key);
}
catch (StringIndexOutOfBoundsException e) {
sdh.log("[add]Invalid item key from server: " + command);
sdh.log("[key]Exception in SyncManagerImpl.processMdifications(:String) invoking the getXMLTagValue(...) method: " + e.toString());
// FIXME: report the error to the server? How to report if the
// key is invalid?
throw new SyncException("Invalid item key from server.");
}
// Get item data
try {
content = getXMLTagValue(command, TAG_DATA);
}
catch (StringIndexOutOfBoundsException e) {
sdh.log("[add]Invalid item data from server. Key: " + key);
sdh.log("[content]Exception in SyncManagerImpl.processMdifications(:String) invoking the getXMLTagValue(...) method: " + e.toString());
// Set error status for this item
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_ERROR));
return cmd;
}
// Process format tag (encryption and encoding)
if (command.indexOf("</Format") >= 0) {
String temp = command;
// FIXME: use getXMLTagValue ?
temp = temp.substring(temp.indexOf("<Format"), temp.indexOf("</Format>") + "</Format>".length());
temp = temp.substring(temp.indexOf(">") + 1);
encryptInfo = temp.substring(0, temp.indexOf("</Format>"));
}
String encryptTypes [] = null;
if (encryptInfo != null && !encryptInfo.equals("")) {
encryptTypes = new StringUtil().split(encryptInfo, ";");
}
try {
if (encryptTypes != null) {
// If ecryption types are specified, apply them
for (int decodeCounter = encryptTypes.length - 1; decodeCounter >= 0; decodeCounter--) {
String currentDecodeType = encryptTypes[decodeCounter];
if (currentDecodeType.equals("b64")) {
content = new String(content.getBytes(),"UTF-8");
content = new String (Base64.decode(content));
}
else if (currentDecodeType.equals("des")) {
desCrypto = new Sync4jDesCrypto(Base64.encode(login.getBytes()));
String decryptedData = null;
decryptedData = desCrypto.decryptData(content.getBytes());
if (decryptedData != null) {
content = decryptedData;
}
}
else if (currentDecodeType.equals("3des")) {
sync3desCrypto = new Sync4j3DesCrypto(Base64.encode(login.getBytes()));
String decryptedData = null;
decryptedData = sync3desCrypto.decryptData(content.getBytes());
if (decryptedData!=null) {
content = decryptedData;
}
}
}// end for
}
else if ( sourceName.equals("xcard") || sourceName.equals("xcal") ) {
// Backward compatibility: SIF data is always b64
content = new String(content.getBytes(), "UTF-8");
content = new String(Base64.decode(content));
}
else {
// Else, the data is text/plain, and the XML special chars are escaped.
content = StringTools.unescapeXml(content);
}
// Set the content
cmd.put(TAG_DATA, content);
}
catch (Exception e) {
sdh.log("[add]Exception in SyncManagerImpl.processModifications(:String) dealing with encryptTypes: " + e.toString());
// XXX: is this an expected exception (i.e. it is caused by a server error, and we
// must only report it, without breaking the sync)?
// Set error status for this item
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_ERROR));
}
return cmd;
}
/**
* Processes the modifications from the received response from server
*
* @param modifications The modification message from server
* @return true if a response message is required, false otherwise
* @throws SyncException
*/
private boolean processModifications(String modifications) throws SyncException {
sdh.log("[LOG]Entering processModifications(:String) in SyncManagerImpl: 'inSynchronize' is " + SyncClient.inSynchronize);
Vector xmlResponse = new Vector();
Vector bodyTags = new Vector();
Vector addTags = new Vector();
Vector replaceTags = new Vector();
Vector deleteTags = new Vector();
//Vector statusTags = new Vector();
String msgId = null;
Record record = null;
boolean ret = false;
// Get message id
try {
msgId = getXMLTagValue(modifications, TAG_MSGID );
}
catch (StringIndexOutOfBoundsException e){
sdh.log("[processModification]Invalid message Id from server: " + modifications);
throw new SyncException("Invalid message from server.");
}
// Get message parts
xmlResponse.addElement(modifications);
bodyTags = getXMLTag(getXMLTag(xmlResponse, TAG_SYNCML), TAG_SYNCBODY );
addTags = getXMLTag(bodyTags, TAG_ADD );
replaceTags = getXMLTag(bodyTags, TAG_REPLACE );
deleteTags = getXMLTag(bodyTags, TAG_DELETE );
//statusTags = getXMLTag(bodyTags, TAG_STATUS );
if (modifications.indexOf("<" + TAG_SYNC + ">") != - 1) {
ret = true;
}
Hashtable cmd = null;
// ADD
for (int i=0, l = addTags.size(); i < l; i++) {
cmd = processModificationCommand( (String)addTags.elementAt(i) );
// If there is an error status,
if(cmd.get(TAG_STATUS) != null){
// Add the status command for this item to the list
addStatusCommand(TAG_ADD, msgId, cmd);
continue;
}
record = new Record( (String)cmd.get(TAG_LOC_URI), ' ',
(String)cmd.get(TAG_DATA));
String bbKey = "";
try {
bbKey = dataStore.setRecord(record, false).getKey();
// Okay, set status code 200
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_OK));
}
catch (DataAccessException e) {
StaticDataHelper.log("[ERROR]Exception in SyncManagerImpl.processModifications(add) invoking setRecord(...): " + e.toString());
//e.printStackTrace();
// Set error status code for this item
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_ERROR));
String msg = e.getMessage();
throw new SyncException(msg);//it should be enough to break the sync process
}
finally {
// Add the status command for this item to the list
addStatusCommand(TAG_ADD, msgId, cmd);
}
if (bbKey != null) {
ret = true;
mappings.put(bbKey, cmd.get(TAG_LOC_URI));
}
}
// REPLACE
for (int i = 0, l = replaceTags.size(); i < l; i++) {
cmd = processModificationCommand((String)replaceTags.elementAt(i));
// If there is an error status,
if(cmd.get(TAG_STATUS) != null){
// Add the status command for this item to the list
addStatusCommand(TAG_REPLACE, msgId, cmd);
continue;
}
record = new Record( (String)cmd.get(TAG_LOC_URI), ' ',
(String)cmd.get(TAG_DATA) );
try {
dataStore.setRecord(record, true);
// Okay, set status code
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_OK));
}
catch(DataAccessException e) {
StaticDataHelper.log("[ERROR]Exception in SyncManagerImpl.processModifications(Replace) invoking setRecord(...): " + e.toString());
e.printStackTrace();
// Set error status code for this item
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_ERROR));
}
finally {
// Add the status command for this item to the list
addStatusCommand(TAG_REPLACE, msgId, cmd);
}
ret = true;
}
// Delete
for (int i=0, l = deleteTags.size(); i < l; i++) {
String key = getXMLTagValue((String) deleteTags.elementAt(i), TAG_LOC_URI );
String cmdId = getXMLTagValue((String) deleteTags.elementAt(i), TAG_CMDID );
record = new Record();
record.setKey(key);
// Set cmd info for the status handling
cmd = new Hashtable();
cmd.put(TAG_CMDID, cmdId);
cmd.put(TAG_LOC_URI, key);
try {
dataStore.deleteRecord(record);
// Okay, set status code
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_OK));
}
catch(DataAccessException e) {
StaticDataHelper.log("[ERROR]Exception in SyncManagerImpl.processMdifications(Delete) invoking deleteRecord(...): " + e.toString());
e.printStackTrace();
// Set error status code for this item
cmd.put(TAG_STATUS, String.valueOf(STATUS_CODE_ERROR));
}
finally {
// Add the status command for this item to the list
addStatusCommand(TAG_DELETE, msgId, cmd);
}
ret = true;
}
// Check Status command from server [TODO]
//checkStatus();
return ret;
}
/**
* Prepares the modification message in SyncML. If includeClientModification
* is <code>true</code> (and, when this method is invoked the first time from
* within the sync() method in this class, this boolean is always <code>true</code>),
* the message will include the client modifications; otherwise
* it will just include the status for the commands sent by the server
* in the previous message. It also returns <code>true</code> if this is the last
* message containing client modifications (as side effect, if includeClientModification
* is false, it returns always false)
*
* @param includeClientModifications true if the message shall include
* client modifications (always true
* by the first invocation in {@link #sync()})
* @param finalTag should <Final/> be included?
*
* @return true if not all client modifications are going to be sent,
* false otherwise
*/
private boolean prepareModificationMessage(boolean includeClientModifications)
throws SyncException {
/*
* TODO: inquire which dimension MAX_ITEM_NUMBER must have
* (it is used as page dimension for the paged vector)
*/
Vector records = new Vector(DataStore.MAX_ITEM_NUMBER);
/*
* when false the <Final/> tag is added at the
* bottom of the SyncML message
*/
boolean moreRecords = true;
/*
* when invoked the first time, always true.
* When true, commands in <Sync> element are
* added in the SyncML message built from
* the mod.xml template: <Add>, <Replace>, <Delete>
*/
if (includeClientModifications) {
String alert;
int alertCode;
/*
* Note that we must synchronize only the sources
* that are aknowledged by the server with a server
* <Alert> command
*/
StaticDataHelper.log("[LOG] serverAlerts: " + serverAlerts);//e.g. scal=200
alert = (String)serverAlerts.get(sourceUri);//e.g. alert == 200, sourceUri == "scal"
if (alert != null) {
alertCode = getSourceAlertCode(sourceUri);
if (alertCode == ALERT_CODE_SLOW) {
//
// Slow Sync!
//
moreRecords = filterRecordsForSlowSync(records);
} else if (alertCode == ALERT_CODE_REFRESH_FROM_SERVER) {
//
// Refresh from server
//
StaticDataHelper.log("Prepare refresh for " + sourceUri);
records = new Vector(); // no items sent for refrsh
moreRecords = false;
} else if (alertCode == ALERT_CODE_ONE_WAY_FROM_SERVER) {
//
// One-way sync from server: no needs of client modifications
//
StaticDataHelper.log("Prepare one-way sync for " + sourceUri);
records = new Vector(); // no items sent for one-way
moreRecords = false;
} else {
//
// Fast Sync!
//
StaticDataHelper.log("Prepare fast sync for " + sourceUri);
//
// NOTE: filterRecordsForFastSync returns items in updated state
//
moreRecords = filterRecordsForFastSync(records);//the 1st time records is null
}
}
else { //if alert == null
moreRecords = false;//no <Final/> tag is set in the SyncML message
StaticDataHelper.log("The server did not sent an Alert for " +
sourceUri +
". The source will not be synchronized.");
}
if (!encryptType.equals("none")) {
records = encryptCryptoRecords(records); //first encrypt
records = encodeB64Records(records); //then encode
}
else if (sourceName.equals("xcard") || sourceName.equals("xcal")) {
records = encodeB64Records(records);
}
else {
records = escapeXMLRecords(records);
}
}
else {//if param includeClientModifications is false
moreRecords = false;//no <Final/> tag is set in the SyncML message
}
StaticDataHelper.log("=== In SyncManagerImpl.prepareModificationMessage() ===");
StaticDataHelper.log("=== START BUILDING THE SYNCML MESSAGE from mod.xml ===");
modificationsXML = messageFormat(modificationsXMLTemplate, "{0}", sessionID);
modificationsXML = messageFormat(modificationsXML, "{10}", getNextMsgId() );
modificationsXML = messageFormat(modificationsXML, "{1}", serverUrl);
modificationsXML = messageFormat(modificationsXML, "{2}", MAX_MSG_SIZE);
modificationsXML = messageFormat(modificationsXML, "{3}", serverUrlStatus);
//includeClientModifications
modificationsXML = messageFormat(modificationsXML, "{4}", prepareSyncTag(records,
sourceUri,
includeClientModifications));
//moreRecords
modificationsXML = messageFormat(modificationsXML, "{5}", ((!moreRecords) ? "<Final/>" : "") );
modificationsXML = messageFormat(modificationsXML, "{6}", deviceId);
modificationsXML = messageFormat(modificationsXML, "{7}", deviceId);
return moreRecords;
}
/**
* Prepare alert message
*
* This method seems actually not to be used anymore
* [14.04.2006]
*
*/
private void prepareAlertMessage() {
alertXML = messageFormat(alertXML, "{0}", sessionID );
alertXML = messageFormat(alertXML, "{10}", getNextMsgId() );
alertXML = messageFormat(alertXML, "{1}", serverUrl );
alertXML = messageFormat(alertXML, "{2}", serverUrl );
alertXML = messageFormat(alertXML, "{3}", serverUrlStatus );
alertXML = messageFormat(alertXML, "{4}", deviceId );
alertXML = messageFormat(alertXML, "{5}", deviceId );
alertXML = messageFormat(alertXML, "{6}", deviceId );
}
/**
* Prepare mapping message
*
**/
private void prepareMappingMessage() {
sdh.log("[LOG]Entering prepareMappingMessage() in SyncManagerImpl: 'inSynchronize' is " + SyncClient.inSynchronize);
String targetRef = null ;
String sourceRef = null ;
StringBuffer mapTag = new StringBuffer();
int cmdId = 1;
int i = 0;
// Build status commands
for (int idx = 0, l = statusCommands.size(); idx < l; idx++) {
mapTag.append("<Status>\n")
.append("<CmdID>" + cmdId++ + "</CmdID>\n")
.append(statusCommands.elementAt(idx))
.append("</Status>\n");
}
// and cleanup the status vector
statusCommands.removeAllElements();
for (Enumeration e = mappings.keys(); e.hasMoreElements(); ) {
if (i == 0) {
mapTag.append("<Map>\n");
mapTag.append("<CmdID>" + cmdId++ + "</CmdID>\n");
mapTag.append("<Target>\n");
mapTag.append("<LocURI>" + sourceUri + "</LocURI>\n");
mapTag.append("</Target>\n");
mapTag.append("<Source>\n");
mapTag.append("<LocURI>" + sourceUri + "</LocURI>\n");
mapTag.append("</Source>\n");
}
sourceRef = (String) e.nextElement();
targetRef = (String) mappings.get(sourceRef);
mapTag.append("<MapItem>\n");
mapTag.append("<Target>\n");
mapTag.append("<LocURI>" + targetRef + "</LocURI>\n");
mapTag.append("</Target>\n");
mapTag.append("<Source>\n");
mapTag.append("<LocURI>" + sourceRef + "</LocURI>\n");
mapTag.append("</Source>\n");
mapTag.append("</MapItem>\n");
i++;
}
if (i > 0) {
mapTag.append("</Map>\n");
}
mapTag.append("<Final/>\n");
mappingXML = messageFormat(mappingXML, "{0}", sessionID );
mappingXML = messageFormat(mappingXML, "{10}", getNextMsgId() );
mappingXML = messageFormat(mappingXML, "{1}", serverUrl );
mappingXML = messageFormat(mappingXML, "{2}", serverUrl );
mappingXML = messageFormat(mappingXML, "{3}", mapTag.toString() );
mappingXML = messageFormat(mappingXML, "{4}", deviceId );
mappingXML = messageFormat(mappingXML, "{5}", deviceId );
}
/**
* Load the resources from the classpath.
*
* @throws IOException
*/
private void loadResources() throws IOException {
Class c = this.getClass();
/*
alertXML = StaticDataHelper.read(
c.getResourceAsStream(XML_ALERT)
);
*/
clientInitXML = StaticDataHelper.read(
c.getResourceAsStream(XML_INITIALIZATION)
);
modificationsXMLTemplate = StaticDataHelper.read(
c.getResourceAsStream(XML_MODIFICATIONS)
);
mappingXML = StaticDataHelper.read(
c.getResourceAsStream(XML_MAPPING)
);
}
/**
* Contructs the alerts for the given databses.
* @param sourceUri
* @return the XML for the SyncML Alert commands
*/
private String createAlerts(String sourceUri) {
StringBuffer sb = new StringBuffer();
String timestamp ="<Next>" + nextTimestamp + "</Next>\n";
if (lastTimestamp != 0l) {
timestamp = "<Last>" + lastTimestamp + "</Last>\n" + timestamp;
}
sb.append("<Alert>\n");
sb.append("<CmdID>1</CmdID>\n");
sb.append("<Data>");
if (alertCode == null) {
if (lastTimestamp != 0) {
sb.append("200");
}
else {
sb.append("201");
}
}
else {
sb.append(alertCode);
}
sb.append("</Data>\n");
sb.append("<Item>\n");
sb.append("<Target><LocURI>");
sb.append(sourceUri);
sb.append("</LocURI>\n");
sb.append(createFilter(MAX_MSG_SIZE));
sb.append("</Target>\n");
sb.append("<Source><LocURI>");
sb.append(sourceUri);
sb.append("</LocURI></Source>\n");
sb.append("<Meta>\n");
sb.append("<Anchor xmlns=\"syncml:metinf\">\n");
sb.append(timestamp);
sb.append("</Anchor>\n");
sb.append("</Meta>\n");
sb.append("</Item>\n");
sb.append("</Alert>");
sb.append("\n");
return sb.toString();
}
/**
* Contructs the alerts for the given databses.
* @param none
* @return the XML for the SyncML Filter command
*/
private String createFilter(String msgSize) {
// FIXME: use a common define.
if ("mail".equals(sourceName)) {
long size = Long.parseLong(msgSize);
size -= 2048;
return new String(
"<Filter>\r\n<Field><Item>\r\n" +
"<Meta><Type>application/vnd.syncml-devinf+xml</Type></Meta>\r\n" +
"<Data>\r\n<Property><PropName>emailitem</PropName>\r\n" +
"<MaxSize>" + size + "</MaxSize>\r\n" +
"<PropParam><ParamName>texttype</ParamName></PropParam>\r\n" +
"</Property></Data>\r\n</Item></Field>\r\n</Filter>");
}
else {
return new String("");
}
}
/**
* Make a String[] with tags matching the search tag.
*
* @param xmlInput XML document to search in
* @param tag to find
* @return find tags
*/
private Vector getXMLTag(Vector xmlInput, String tag) throws SyncException {
Vector xmlReturn = null ;
String xmlInputTag = null ;
String endTag = null ;
endTag = "</" + tag + ">" ;
xmlReturn = new Vector();
try {
for (int j=0, l = xmlInput.size(); j < l; j++) {
xmlInputTag = (String) xmlInput.elementAt(j);
//
// tag without namespace
// or tag with namespace
//
while (xmlInputTag.indexOf("<" + tag + ">") != -1 ||
xmlInputTag.indexOf("<" + tag + " ") != -1) {
xmlReturn.addElement(getXMLTagValue(xmlInputTag, tag));
xmlInputTag =
xmlInputTag.substring(
xmlInputTag.
indexOf(endTag) + endTag.length());
}
}
} catch (Exception e) {
throw new SyncException("Parsing XML error, TAG " +
tag +
" - : " +
e.getMessage() );
}
return xmlReturn;
}
/**
* Make a String by value of <i>tag</i>.
*
* @param xml xml msg
* @param tag tag to find + sourceType +
* @return tag value
*/
private String getXMLTagValue(String xml, String tag) {
String startTag = null;
String endTag = null;
startTag = "<" + tag + ">";
endTag = "</" + tag + ">";
return xml.substring(xml.indexOf(startTag) + startTag.length(), xml.indexOf(endTag));
}
/**
* Retrieves the modified items since the last synchronization.
* These are passed 'by reference' because the value of a Java
* reference (<code>records</code>) is passed, and so the vector
* <code>records</code> is updated
*
* @param records The empty vector of records to be filled
*
* @return boolean <code>true</code> if there are more records
* to be added
*/
private boolean filterRecordsForFastSync(Vector records) throws SyncException {
boolean moreRecords;
try {
moreRecords = dataStore.getNextRecords(records, '|');
if (moreRecords)
return moreRecords;
dataStore.resetModificationCursor();
} catch (DataAccessException e) {
throw new SyncException(e.getMessage());
}
/*
catch (Exception e) {
StaticDataHelper.log("<<<>>>Exception in SyncManagerImpl.filterRecordsForFastSync(Vector) --> " + e.toString());
//throw new Exception("SyncManagerImpl.filterRecordsForFastSync(Vector): " + e.toString());
}
*/
return moreRecords;
}
/**
* Retrieves all items belonging to the given datastore, except those related to deleted items.
* regardless their modification status. It is used for slow sync.
*
* @return all items in the source
*/
private boolean filterRecordsForSlowSync(Vector records)
throws SyncException {
try {
boolean moreElements = dataStore.getNextRecords(records);
for (int i = 0, l = records.size(); i < l; ++i) {
((Record)records.elementAt(i)).setState(RECORD_STATE_UPDATED);
}
return moreElements;
} catch (Exception e) {
e.printStackTrace();
throw new SyncException(e.getMessage());
}
}
/**
* return Sync tag about sourceUri
*
* @param records records to sync
* @param sourceURI source uri
* @return sync tag value
*/
private String prepareSyncTag(Vector records,
String sourceURI,
boolean sendingClientModifications) throws SyncException {
StringBuffer addItems = new StringBuffer();
StringBuffer replaceItems = new StringBuffer();
StringBuffer deleteItems = new StringBuffer();
StringBuffer addCmd = new StringBuffer();
StringBuffer replaceCmd = new StringBuffer();
StringBuffer deleteCmd = new StringBuffer();
StringBuffer syncTag = new StringBuffer();
int cmdId = 2;
Record record = null;
String encryptXmlTag = "";
if (sourceName.equals("xcard") || sourceName.equals("xcal")) {
encryptXmlTag = "<Format xmlns=\'syncml:metinf\'>b64</Format>";
}
if (encryptType != null && !encryptType.equals("none")) {
encryptXmlTag = "<Format xmlns=\'syncml:metinf\'>"+encryptType+";b64</Format>";
}
//
// NOTE: for JDK 1.1.8 compatibility we cannot use StringBuffer.append(StringBuffer)
//
// FIXME: this test is not very smart....
if(msgID == 2) {
syncTag.append("<Status>\n")
.append("<CmdID>" + cmdId++ + "</CmdID>\n")
.append("<MsgRef>1</MsgRef><CmdRef>1</CmdRef><Cmd>Alert</Cmd>\n")
.append("<TargetRef>")
.append(sourceURI)
.append("</TargetRef>\n")
.append("<SourceRef>")
.append(sourceURI)
.append("</SourceRef>\n")
.append("<Data>200</Data>\n")
.append("<Item>\n")
.append("<Data>" + "\n")
.append("<Anchor xmlns=\"syncml:metinf\"><Next>")
.append(nextTimestamp)
.append("</Next></Anchor>\n")
.append("</Data>\n")
.append("</Item>\n")
.append("</Status>\n");
}
// Build status commands
for (int i = 0, l = statusCommands.size(); i < l; i++) {
syncTag.append("<Status>\n")
.append("<CmdID>" + cmdId++ + "</CmdID>\n")
.append(statusCommands.elementAt(i))
.append("</Status>\n");
}
// and cleanup the status vector
statusCommands.removeAllElements();
// Build items
for (int i = 0, l = records.size(); i < l; i++) {
record = (Record)records.elementAt(i);
try {
switch (record.getState()) {
case RECORD_STATE_DELETED:
deleteItems.append("<Item>\n")
.append("<Source><LocURI>")
.append(record.getKey())
.append("</LocURI></Source>\n")
.append("</Item>\n");
break;
case RECORD_STATE_UPDATED:
replaceItems.append("<Item>\n")
.append("<Source><LocURI>")
.append(record.getKey())
.append("</LocURI></Source>\n")
.append("<Data>")
.append(record.getUid())
.append("</Data>\n")
.append("</Item>\n");
break;
case RECORD_STATE_NEW:
addItems.append("<Item>\n")
.append("<Source><LocURI>")
.append(record.getKey())
.append("</LocURI></Source>\n")
.append("<Data>")
.append(record.getUid())
.append("</Data>\n")
.append("</Item>\n");
break;
}// end switch
} catch (OutOfMemoryError oom) {
throw new SyncException("out-of-memory");
}
}// next i
int syncCmdId = cmdId++;
// Build commands
if (addItems.length() > 0) {
addCmd.append("<Add>\n")
.append("<CmdID>" + cmdId++ + "</CmdID>\n")
.append("<Meta><Type xmlns=\"syncml:metinf\">")
.append(sourceType).append("</Type>")
.append(encryptXmlTag)
.append("</Meta>\n")
.append(addItems.toString())
.append("\n</Add>\n");
}
if (replaceItems.length() > 0) {
replaceCmd.append("<Replace>\n")
.append("<CmdID>" + cmdId++ + "</CmdID>\n")
.append("<Meta><Type xmlns=\"syncml:metinf\">")
.append(sourceType).append("</Type>")
.append(encryptXmlTag)
.append("</Meta>\n")
.append(replaceItems.toString())
.append("\n</Replace>\n");
}
if (deleteItems.length() > 0) {
deleteCmd.append("<Delete>\n")
.append("<CmdID>" + cmdId++ + "</CmdID>\n")
.append(deleteItems.toString())
.append("\n</Delete>\n");
}
if (sendingClientModifications) {
syncTag.append("<Sync>\n")
.append("<CmdID>" + syncCmdId + "</CmdID>\n")
.append("<Target><LocURI>")
.append(sourceURI)
.append("</LocURI></Target>\n")
.append("<Source><LocURI>")
.append(sourceURI)
.append("</LocURI></Source>\n")
.append(addCmd.toString())
.append(replaceCmd.toString())
.append(deleteCmd.toString())
.append("</Sync>");
}
addCmd = null;
replaceCmd = null;
deleteCmd = null ;
return syncTag.toString();
}
/**
* Returns the server alert code for the given source
*
* @param sourceURI the source
*
* @return the server alert code for the given source or -1 if it is not
* found/parsable
*/
private int getSourceAlertCode(String sourceURI) {
try {
return Integer.parseInt((String)serverAlerts.get(sourceURI));
} catch (Throwable t) {
t.printStackTrace();
StaticDataHelper.log("ERROR: unrecognized server alert code ("
+ serverAlerts.get(sourceURI)
+ ") for "
+ sourceURI);
}
return -1;
}
/**
* Set variable in XML msg.
*
* @param msgXML msg XML
* @param variable variable name
* @param variableValue variable value
* @return msg XML with setting variable value
*/
private String messageFormat(String msgXML, String variable, String variableValue) {
String msgXMLBefore = null;
String msgXMLAfter = null;
msgXMLBefore = msgXML.substring(0, msgXML.indexOf(variable));
msgXMLAfter = msgXML.substring(msgXML.indexOf(variable) + variable.length());
return (msgXMLBefore + variableValue + msgXMLAfter);
}
/**
* Encode Records in Base64
*
* @param Vector records
* @return Vector records
*/
private Vector encodeB64Records(Vector records) {
String content = null;
for (int i = 0, l = records.size(); records != null && i < l; i++) {
content = ((Record)records.elementAt(i)).getUid();
if (content != null && content.length() > 0) {
((Record)records.elementAt(i)).setUid(new String(Base64.encode(content.getBytes())));
}
}
return records;
}
/**
* Escape XML characters in Records.
*
* @param Vector records
* @return Vector records
*/
private Vector escapeXMLRecords(Vector records) {
String content = null;
for (int i = 0, l = records.size(); records != null && i < l; i++) {
content = ((Record)records.elementAt(i)).getUid();
if (content != null && content.length() > 0) {
((Record)records.elementAt(i)).setUid(
new String(StringTools.escapeXml(content)));
}
}
return records;
}
/**
* Encrypts Records according to algorithm.
*
* @param Vector records
* @return Vector records
*/
private Vector encryptCryptoRecords(Vector records) {
String content = null;
for (int i = 0, l = records.size(); records != null && i < l; i++) {
content = ((Record) records.elementAt(i)).getUid();
if (content != null && content.length() > 0) {
if (desCrypto!=null) {
((Record)records.elementAt(i)).setUid(new String(desCrypto.encryptData(content)));
} else if (sync3desCrypto != null) {
((Record)records.elementAt(i)).setUid(new String(sync3desCrypto.encryptData(content)));
}
}
}
return records;
}
// Reset the message ID counter.
private String resetMsgId(){
msgID = 1;
return "1";
}
// Return the next message ID to use.
private String getNextMsgId() {
return String.valueOf(msgID++);
}
}