Package com.funambol.syncclient.spds

Source Code of com.funambol.syncclient.spds.SyncManagerImpl

/*
* 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++);
    }
}
TOP

Related Classes of com.funambol.syncclient.spds.SyncManagerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.