com.funambol.LDAP.engine.source.AbstractLDAPSyncSource.java Source code

Java tutorial

Introduction

Here is the source code for com.funambol.LDAP.engine.source.AbstractLDAPSyncSource.java

Source

/**
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
package com.funambol.LDAP.engine.source;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;

import javax.naming.directory.Attributes;

import org.apache.commons.lang.StringUtils;

import com.funambol.LDAP.dao.ContactDAOInterface;
import com.funambol.LDAP.exception.LDAPAccessException;
import com.funambol.LDAP.manager.LDAPManagerFactory;
import com.funambol.LDAP.manager.LdapManagerInterface;
import com.funambol.LDAP.manager.impl.LDAPState;
import com.funambol.LDAP.security.LDAPUser;
import com.funambol.LDAP.utils.Constants;
import com.funambol.common.pim.contact.Contact;
import com.funambol.framework.core.AlertCode;
import com.funambol.framework.engine.SyncItem;
import com.funambol.framework.engine.SyncItemImpl;
import com.funambol.framework.engine.SyncItemKey;
import com.funambol.framework.engine.SyncItemState;
import com.funambol.framework.engine.source.AbstractSyncSource;
import com.funambol.framework.engine.source.SyncContext;
import com.funambol.framework.engine.source.SyncSource;
import com.funambol.framework.engine.source.SyncSourceException;
import com.funambol.framework.logging.FunambolLogger;
import com.funambol.framework.logging.FunambolLoggerFactory;
import com.funambol.framework.security.Sync4jPrincipal;
import com.funambol.framework.server.Sync4jDevice;
import com.funambol.framework.server.store.PersistentStore;
import com.funambol.framework.server.store.PersistentStoreException;
import com.funambol.framework.tools.beans.LazyInitBean;
import com.funambol.server.config.Configuration;

/**
 * This class implements a LDAP <i>SyncSource</i>
 *
 * @author  <a href='mailto:fipsfuchs _@ users.sf.net'>Philipp Kamps</a>
 * @author  <a href='mailto:julien.buratto _@ subitosms.it'>Julien Buratto</a>
 * @author  <a href='mailto:gdartigu _@ smartjog.com'>Gilles Dartiguelongue</a>
 * @author  <a href='mailto:robipolli _@ gmail.com'>Roberto Polli</a>
 * @version $Id$
 *
 * Change log:
 * - Julien Buratto: reintroduced the use of %principal to include the principal ID in the sync tree (ldap base)
 * - Julien Buratto: introduced %e to include the email address in the sync tree (ldap base)
 */
public abstract class AbstractLDAPSyncSource extends AbstractSyncSource
        implements SyncSource, Serializable, LazyInitBean {
    // ----------------------------------------------------------- Constants

    /**
     * 
     */
    private static final long serialVersionUID = -6842763250018970751L;
    protected FunambolLogger logger = FunambolLoggerFactory.getLogger(Constants.LOGGER_LDAP_SOURCE);

    // -------------------------------------------------------- Protected Bean Data
    protected TimeZone serverTimeZone = null;
    protected String baseDn;
    protected String ldapUser;
    protected String ldapPass;

    //
    // dynamic data
    //
    protected int syncMode;

    // manager
    protected ContactDAOInterface contactDAO = null;
    protected LDAPState ldapState = null;

    /**
     * softDelete should be enabled and supported by the DAO
     */
    private boolean softDelete = false;

    public boolean isSoftDelete() {
        return softDelete;
    }

    public void setSoftDelete(boolean softDelete) {
        this.softDelete = softDelete;
    }

    private String entryFilter;

    public void setEntryFilter(String entryFilter) {
        this.entryFilter = entryFilter;
    }

    public String getEntryFilter() {
        return entryFilter;
    }

    private String providerUrl;

    public String getProviderUrl() {
        return providerUrl;
    }

    public void setProviderUrl(String providerUrl) {
        this.providerUrl = providerUrl;
    }

    protected String userSearch = null;

    public void setUserSearch(String s) {
        userSearch = s;
    }

    public String getUserSearch() {
        return (userSearch != null) ? userSearch : "";
    }

    private String ldapInterfaceClassName;

    public void setLdapInterfaceClassName(String ldapInterfaceClassName) {
        this.ldapInterfaceClassName = ldapInterfaceClassName;
    }

    public String getLdapInterfaceClassName() {
        return ldapInterfaceClassName;
    }

    private String daoName;

    public String getDaoName() {
        return daoName;
    }

    public void setDaoName(String daoName) {
        this.daoName = daoName;
    }

    protected String deviceTimeZoneDescription = null;
    protected TimeZone deviceTimeZone = null;

    public TimeZone getDeviceTimeZone() {
        return deviceTimeZone;
    }

    public void setDeviceTimeZone(TimeZone deviceTimeZone) {
        this.deviceTimeZone = deviceTimeZone;
    }

    private String dbName;
    private boolean followReferral;
    private boolean connectionPooling;

    public void setFollowReferral(boolean followReferral) {
        this.followReferral = followReferral;
    }

    public boolean isFollowReferral() {
        return followReferral;
    }

    public void setConnectionPooling(boolean isConnectionPooling) {
        this.connectionPooling = isConnectionPooling;
    }

    public boolean isConnectionPooling() {
        return connectionPooling;
    }

    private String deviceCharset = null;

    public String getDeviceCharset() {
        return deviceCharset;
    }

    public void setDeviceCharset(String deviceCharset) {
        this.deviceCharset = deviceCharset;
    }

    protected Sync4jPrincipal principal = null;

    public Sync4jPrincipal getPrincipal() {
        if (logger.isTraceEnabled()) {
            logger.trace("getPrincipal(" + principal + " , ...)");
            if (principal != null)
                logger.trace("my username is [" + principal.getUsername() + "]"); //rpolli
        }
        return principal;
    }

    public void setPrincipal(Sync4jPrincipal principal) {
        this.principal = principal;
    }

    /**
     * The context of the sync
     */
    protected SyncContext syncContext = null;

    protected SyncContext getSyncContext() {
        if (logger.isDebugEnabled())
            logger.debug("getSyncContext(" + syncContext + " , ...)");
        return syncContext;
    }

    // TODO: cache variables. think about how to implement a cache mechanism
    protected List<SyncItem> allItems = new ArrayList<SyncItem>();
    protected List<SyncItem> newItems = new ArrayList<SyncItem>();
    protected List<SyncItem> deletedItems = new ArrayList<SyncItem>();
    protected List<SyncItem> updatedItems = new ArrayList<SyncItem>();

    protected List<String> allUids = new ArrayList<String>();
    protected List<String> newUids = new ArrayList<String>();
    protected List<String> deletedUids = new ArrayList<String>();
    protected List<String> updatedUids = new ArrayList<String>();
    private boolean initialized;

    // ---------------------------------------------------------- Properties

    /**
     * Set the class to use ad ContactDAO
     * - PiTypeContactDAO
     * - ContactDAO
     */
    protected void initContactDAO(String className) throws SyncSourceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Getting class instance: " + getDaoName());
        }
        this.contactDAO = (ContactDAOInterface) LDAPManagerFactory.createContactDAO(getDaoName());
    }

    // -------------------------------------------------------- Constructors
    public AbstractLDAPSyncSource() {
        super();
        if (logger.isDebugEnabled())
            logger.debug("AbstractLDAPSyncSource()");
    }

    public AbstractLDAPSyncSource(String name) {
        super(name);
        if (logger.isDebugEnabled())
            logger.debug("AbstractLDAPSyncSource(" + name + " , ...)");
    }

    // ------------------------------------------------------ Public methods
    /**
     * Initialize the ContactDAO and manage soft delete filter
     */
    public void init() {
        if (!initialized) { // needed to initialize bean in FCTF
            /*
             * this is a lazy init bean: here you should set attributes
             * 1- not set by the standard bean
             * 2- independent from the principal (eg. user), which is still null
             */

            if (logger.isInfoEnabled()) {
                logger.info("AbstractLDAPSyncSource.init()");
            }

            if (getDaoName() != null) {
                try {
                    initContactDAO(getDaoName());

                    if (isSoftDelete() && contactDAO.getSoftDeleteAttribute() != null) {
                        setEntryFilter(
                                String.format("(&%s%s)", getEntryFilter(), contactDAO.getSoftDeleteFilter()));
                    }

                } catch (SyncSourceException e) {
                    if (logger.isErrorEnabled())
                        logger.error(e.getMessage());
                }
            }

            if (getLdapInterfaceClassName() == null) {
                logger.error("No LDAP Server type retrieved!!!");
            }
            initialized = true;
        }
    }

    /**
     * Adds a SyncItem object (representing a contact).
     *
     * @param syncItem the SyncItem representing the contact
     *
     * @return a newly created syncItem based on the input object but with its
     *         status set at SyncItemState.NEW and the GUID retrieved by the
     *         ldapId of the newly created LDAP entry
     */
    public SyncItem addSyncItem(SyncItem syncItem) throws SyncSourceException {
        logger.info("addSyncItem(" + principal + " , " + syncItem.getKey().getKeyAsString() + ")");

        try {
            SyncItem itemOnServer = getSyncItemFromId(syncItem.getKey());

            // Adds the contact, wraps it in sync information and uses it to
            // create a new SyncItem which is the return value of this method
            if (itemOnServer != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Error: item still present on server" + syncItem);
                }
                throw new SyncSourceException("Error: item still present on server" + syncItem.getKey());
            }

            String content = getContentFromSyncItem(syncItem);
            String contentType = syncItem.getType();

            SyncItemImpl serverSyncItem = new SyncItemImpl(this, //syncSource
                    syncItem.getKey().getKeyAsString(), //key
                    null, //mappedKey
                    SyncItemState.NEW, //state
                    content.getBytes(), //content
                    null, //format
                    contentType, //type
                    syncItem.getTimestamp() //timestamp
            );
            serverSyncItem.setType(info.getPreferredType().type);

            ++howManyAdded;
            ArrayList<SyncItem> aSyncItem = new ArrayList<SyncItem>();
            aSyncItem.add(serverSyncItem);

            ldapState.commitNewItem(aSyncItem);

            serverSyncItem.setType(info.getPreferredType().type);

            //allItems.add(serverSyncItem);
            newItems.add(serverSyncItem); // here just to avoid another ldapsearch

            return serverSyncItem;
        } catch (Exception e) {
            // TODO here we must put an LDAPStateException to make this class independent from the chosen driver
            // TODO add logging
            e.printStackTrace();
            throw new SyncSourceException("Error adding the item " + syncItem + "LDAP Error: ", e);
        }
    }

    /**
     * Updates a SyncItem object (representing a contact).
     *
     * @param syncItem the SyncItem representing the contact
     *
     * @return a newly created syncItem based on the input object but with its
     *         status set at SyncItemState.UPDATED and the GUID retrieved by the
     *         back-end
     */
    public SyncItem updateSyncItem(SyncItem syncItem) throws SyncSourceException {
        logger.info("updateSyncItem(" + principal + " , " + syncItem.getKey().getKeyAsString() + ")");

        SyncItem itemOnServer = getSyncItemFromId(syncItem.getKey());

        if (itemOnServer != null) {
            String content = getContentFromSyncItem(syncItem);

            SyncItemImpl serverSyncItem = new SyncItemImpl(this, //syncSource
                    syncItem.getKey().getKeyAsString(), //key
                    //               syncItem.getParentKey()   , // not specified in
                    null, //mappedKey
                    SyncItemState.UPDATED, //state
                    content.getBytes(), //content
                    syncItem.getFormat(), //format
                    syncItem.getType(), //type
                    null //timestamp
            );

            ++howManyUpdated;
            ArrayList<SyncItem> aSyncItem = new ArrayList<SyncItem>();
            aSyncItem.add(serverSyncItem);
            ldapState.commitUpdatedItems(aSyncItem);

            // updatedItems.add(syncItem);
            // itemOnServer = syncItem;
            syncItem.setType(info.getPreferredType().type);
            return syncItem;
        } else {
            throw new SyncSourceException("Item is not on server");
        }
    }

    /**
     * get item with the given GUID
     * uses allItems as a cache to avoid further ldapsearch
     * TODO this method searches twice: syncItemKey -> ldapUid -> ldapItem
     * @see SyncSource
     */
    public SyncItem getSyncItemFromId(SyncItemKey syncItemKey) throws SyncSourceException {
        String id = null;
        SyncItem syncItem = null;

        if (logger.isInfoEnabled())
            logger.info("getSyncItemsFromId(" + principal + ", " + syncItemKey + ")");
        List<String> item = ldapState.getOneItem(syncItemKey); // this method is redundant, search directly for the key
        if (!item.isEmpty()) {
            Attributes entry = ldapState.getLDAPEntryById(item.get(0));
            if (entry != null) {
                Contact c = contactDAO.createContact(entry);
                syncItem = createItem(syncItemKey.getKeyAsString(), c, SyncItemState.NEW);
            } else {
                logger.warn("Unable to get the LDAP entry for id: " + id);
            }

        }
        return syncItem;
        //return getSyncItems(contactDAO.getContact( ldapState.getOneItem(syncItemKey).get(0) ), SyncItemState.NEW);
    }

    /**
     * get the item GUIDs newly created on server:
     * it freshen the newItems array
     * TODO come gestisce il timezone questa classe?
     * @see SyncSource
     * @return GUIDs
     */
    public SyncItemKey[] getNewSyncItemKeys(Timestamp since, Timestamp until) throws SyncSourceException {
        logger.info("getNewSyncItemKeys(" + principal + " , " + since + " , " + until + ")");
        Timestamp sinceUTC = since;

        // timestamp to UTC if the device has a timezone. FIXME this should'n work
        if (deviceTimeZone != null) {
            sinceUTC = normalizeTimestamp(since);
        }

        if (syncMode == AlertCode.ONE_WAY_FROM_SERVER) {
            return getAllSyncItemKeys();
        }

        List<String> ids = ldapState.getNewItems(sinceUTC, until);
        logger.info("Get NEW sync items ( " + ids.size() + " ) since ( " + sinceUTC + " ) until ( " + until + " )");

        List<Contact> contacts = new ArrayList<Contact>();
        for (String id : ids) {
            Attributes entry = ldapState.getLDAPEntryById(id);
            contacts.add(contactDAO.createContact(entry));
        }
        newItems = getSyncItems(contacts, SyncItemState.NEW);

        return extractKeys(newItems);
    }

    /**
     * @see SyncSource
     */
    public SyncItemKey[] getDeletedSyncItemKeys(Timestamp since, Timestamp until) throws SyncSourceException {
        logger.info("getDeletedSyncItemKeys(" + principal + " , " + since + " , " + until + ")");

        if (deviceTimeZone != null) {
            since = normalizeTimestamp(since);
        }

        List<String> ids = ldapState.getDeletedItems(this, since);

        logger.info("Got DELETED sync items ( " + ids.size() + " ) since ( " + since + " )");

        deletedItems = new ArrayList<SyncItem>();

        Iterator<String> it = ids.iterator();
        while (it.hasNext()) {
            deletedItems.add(new SyncItemImpl(this, it.next(), SyncItemState.DELETED));
        }

        return extractKeys(deletedItems);
    }

    /**
     * @see SyncSource
     * @return GUIDs of updated items
     */
    public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp since, Timestamp until) throws SyncSourceException {
        logger.info("getUpdatedSyncItemKeys(" + principal + " , " + since + " , " + until + ")");

        if (deviceTimeZone != null) {
            since = normalizeTimestamp(since);
        }

        List<String> ids = ldapState.getModifiedItems(since, until);
        logger.info("Get UPDATED sync items ( " + ids.size() + " ) since ( " + since + " )");
        List<Contact> contacts = new ArrayList<Contact>();
        for (String id : ids) {
            Attributes entry = ldapState.getLDAPEntryById(id);
            contacts.add(contactDAO.createContact(entry));
        }
        updatedItems = getSyncItems(contacts, SyncItemState.UPDATED);
        return extractKeys(updatedItems);
    }

    /**
     * 1- fill allItems with all the LDAP Contacts
     * 2- return an array of GUID
     * @see SyncSource
     * @return array of GUID
     */
    /*public SyncItemKey[] getAllSyncItemKeys_ori()
     throws SyncSourceException {
     logger.info("getAllSyncItemKeys(" + principal + ")");
        
     List<String> ids = ldapState.getAllEntriesLdapId(); // this function returns the LdapId of the entries
     logger.info("Got ALL sync items ( " + ids.size() + " )");
     allItems = getSyncItems(contactDAO.getContacts(ids), SyncItemState.NEW);
        
     SyncItemKey[] keys = new SyncItemKey[allItems.size()];
        
     for (int i=0; i<allItems.size(); i++) {
     keys[i] = allItems.get(i).getKey();
     }
     return keys; //extractKeys(allItems);
     }*/
    /**
     * 1- fill allItems with all the LDAP Contacts
     * 2- return an array of GUID
     * @see SyncSource
     * @return array of GUID
     */
    public SyncItemKey[] getAllSyncItemKeys() throws SyncSourceException {
        logger.info("getAllSyncItemKeys(" + principal + ")");

        List<String> ids = ldapState.getAllEntriesLdapId(); // this function returns the LdapId of the entries. this is bad
        if (logger.isInfoEnabled())
            logger.info("Got ALL sync items ( " + ids.size() + " )");
        //      allItems = getSyncItems(contactDAO.getContacts(ids), SyncItemState.NEW);

        SyncItemKey[] keys = new SyncItemKey[ids.size()];

        for (int i = 0; i < ids.size(); i++) {
            keys[i] = new SyncItemKey(ids.get(i));
        }

        return keys; //extractKeys(allItems);
    }

    /**
     * @see SyncSource
     * TODO returns the key of the syncItem passed
     */
    public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem syncItem) throws SyncSourceException {
        logger.info("getSyncItemKeysFromTwin()");

        return new SyncItemKey[0];
    }

    /**
     * @see SyncSource
     */
    public void removeSyncItem(SyncItemKey syncItemKey, Timestamp time, boolean softDelete)
            throws SyncSourceException {
        if (softDelete) {
            // Ignore soft delete
            logger.warn("Soft Delete not implemented yet!");
            return;
        } else {
            removeSyncItem(syncItemKey, time);
        }
    }

    /**
     * A string representation of this object
     */
    public String toString() {
        StringBuffer sb = new StringBuffer(super.toString());

        sb.append(" - {name: ").append(getName());
        sb.append(" type: ").append(type);
        sb.append(" uri: ").append(getSourceURI());
        sb.append("}");
        return sb.toString();
    }

    /**
     * SyncSource's beginSync()
     * @param syncContext @see SyncSource
     */
    public void beginSync(SyncContext syncContext) throws SyncSourceException {
        logger.info("AbstractLDAPSyncSource beginSync()");
        // FCTF won't call init() so we need it for testing
        init();
        super.beginSync(syncContext);

        this.syncMode = syncContext.getSyncMode();
        this.syncContext = syncContext;
        this.principal = syncContext.getPrincipal();

        if (contactDAO == null) {
            throw new SyncSourceException("ContactDAO is unset. Please provide a value in configuration");
        }
        if (!initializeManager()) {
            throw new SyncSourceException("LdapState is unset. Please provide a value in configuration");
        }

        if (logger.isInfoEnabled())
            logger.info("beginSync for LDAPConnector ( " + this.principal + " ) mode ( " + syncContext.getSyncMode()
                    + " )" + " since (" + syncContext.getSince() + ";" + syncContext.getTo() + ")");

        setDeviceInfo(syncContext);

        logger.info("AbstractLDAPSyncSource beginSync end");
    }

    /**
     * SyncSource's endSync()
     */
    public void endSync() throws SyncSourceException {
        logger.info("endSync()");
        super.endSync();
        /*
          if ( !newItems.isEmpty() ) { // XXX now it should be empty items are added in real-time. think about drawbacks!!!!
          logger.info("endSync: Commiting " + howManyAdded + " new items to the LDAP server");
          ldapState.commitNewItems( newItems );
          } */

        if (!deletedItems.isEmpty()) {
            if (logger.isInfoEnabled())
                logger.info("endSync: Commiting " + howManyDeleted + " deleted items to the LDAP server");
            ldapState.commitDeletedItems(deletedItems);
        }

        /*
          if ( !updatedItems.isEmpty() ) {
          logger.info("endSync: Commiting " + howManyUpdated + " updated items to the LDAP server");
          ldapState.commitUpdatedItems( updatedItems );
          }
         */

        ldapState.getManager().close();

        if (logger.isInfoEnabled())
            logger.info("endSync for LDAPConnector (" + this.principal + ")");
    }

    /**
     * @see SyncSource
     */
    public void commitSync() throws SyncSourceException {
        logger.info("commitSync =========================================");
        // TODO not useful yet but keeping for eventual future use
    }

    /**
     * @see SyncSource
     */
    public void setOperationStatus(String operation, int statusCode, SyncItemKey[] keys) {

        StringBuffer message = new StringBuffer("Received status code '");
        message.append(statusCode).append("' for a '");
        message.append(operation).append("'");
        message.append(" for this items: ");

        for (int i = 0; i < keys.length; i++) {
            message.append("\n- " + keys[i].getKeyAsString());
        }

        logger.info(message.toString());
    }

    //
    // bean getters/setters
    //

    public String getLdapBase() {
        return baseDn;
    }

    public String getLdapPass() {
        return ldapPass;
    }

    public String getLdapUser() {
        return ldapUser;
    }

    public void setLdapBase(String string) {
        baseDn = string;
    }

    public void setLdapPass(String string) {
        ldapPass = string;
    }

    public void setLdapUser(String string) {
        ldapUser = string;
    }

    public String getDbName() {
        return dbName;
    }

    public void setDbName(String string) {
        dbName = string;
    }

    public TimeZone getServerTimeZone() {
        return serverTimeZone;
    }

    public void setServerTimeZone(TimeZone t) {
        this.serverTimeZone = t;
    }

    public void setLdapUrl(String ldapUrl) {
        this.providerUrl = ldapUrl;
    }

    public String getLdapUrl() {
        return providerUrl;
    }

    // ------------------------------------------------------ Protected methods
    /**
     * Removes the item with the given itemKey marking the item deleted with the
     * give time.
     * @param syncItemKey the key of the item to remove
     * @param time the deletion time
     */
    protected void removeSyncItem(SyncItemKey syncItemKey, Timestamp time) throws SyncSourceException {

        if (logger.isInfoEnabled()) {
            logger.info("removeSyncItem(" + principal + " , " + syncItemKey + " , " + time + ")");
        }
        SyncItem syncItem = getSyncItemFromId(syncItemKey);
        if (logger.isTraceEnabled())
            logger.trace("SyncItem is : " + syncItem);

        if (syncItem != null) // && allItems.contains(syncItem))
        {

            ++howManyDeleted;
            deletedItems.add(syncItem);

            //allItems.remove(syncItem);
        }

    }

    /**
     * Expand bean fields userSearch, baseDn, providerUrl with funambol principal data.
     * With a standard officer, replace 
     *  - username =~ %u@%d
     *  - username = %s
     *  - principal = %principal
     *  
     * Using LdapUserProvisioningOfficer (LDAPUser) enables 
     * - setting providerUrl=psRoot .
     * - basedn = %D
     * @param principal
     */
    protected void expandSearchAndBaseDn(Sync4jPrincipal principal) {
        if (principal != null) {
            String myPrincipalID = "" + principal.getId();
            baseDn = baseDn.replaceAll("%principal", myPrincipalID);

            String username = principal.getUsername();

            // manage LDAPUser attributes
            if (principal.getUser() instanceof LDAPUser) {
                LDAPUser user = (LDAPUser) principal.getUser();

                if (StringUtils.isNotEmpty(user.getPsRoot())) {
                    providerUrl = user.getPsRoot();
                    baseDn = "";
                    if (logger.isDebugEnabled()) {
                        logger.debug("Connecting to " + providerUrl + "/" + baseDn + "?" + getUserSearch());
                    }
                    return;
                } else {
                    if (StringUtils.isNotEmpty(user.getUserDn())) {
                        baseDn = baseDn.replaceAll("%D", user.getUserDn());
                    }
                }
            }

            if (username != null) {
                // if username  is an email substitute %u e %d in baseDn:  
                // eg. basedn: dc=%d, o=babel, dc=top
                // eg. basedn: dc=%d, o=referral
                // eg. ldapUrl: ldap://%d.babel.it:234/
                if (username.matches(Constants.EMAIL_PATTERN)) {
                    String tmp[] = username.split("@");
                    String myUsername = tmp[0];
                    String myDomain = (tmp.length > 1) ? tmp[1] : ""; // if domain is not set, use just %u
                    if (logger.isTraceEnabled()) {
                        logger.trace("username is [" + username + "," + myUsername + ", " + myDomain + "]");
                    }

                    // expand %u and %d in ldapUrl and baseDn
                    // this enables elastic funambol support:D
                    baseDn = baseDn.replaceAll("%u", myUsername).replaceAll("%d", myDomain);
                    providerUrl = providerUrl.replaceAll("%u", myUsername).replaceAll("%d", myDomain);
                    setUserSearch(getUserSearch().replaceAll("%u", myUsername).replaceAll("%d", myDomain));
                }

                // now expand %s
                if (baseDn.contains("%s"))
                    baseDn = baseDn.replaceAll("%s", username);
                if (providerUrl.contains("%s"))
                    providerUrl = providerUrl.replaceAll("%s", username);
                if (getUserSearch().contains("%s"))
                    setUserSearch(getUserSearch().replaceAll("%s", username));
                if (principal.getUser() != null) {
                    baseDn = baseDn.replaceAll("%e", principal.getUser().getEmail());
                }
            }

        }

        if (logger.isTraceEnabled()) {
            logger.trace("Connecting to " + providerUrl + "/" + baseDn + "?" + getUserSearch());
        }

    }

    /**
     * Extracts the content from a syncItem.
     *
     * @param syncItem
     * @return as a String object
     * @see com.funambol.foundation.engine.source
     */
    protected String getContentFromSyncItem(SyncItem syncItem) {

        byte[] itemContent = syncItem.getContent();

        // Add content processing here, if needed

        return new String(itemContent == null ? new byte[0] : itemContent);
    }

    /**
     * Create Ldap/DB manager for the syncsource
     * with dynamic information about the syncSource
     */
    protected boolean initializeManager() {
        boolean ret = false;
        if (logger.isDebugEnabled())
            logger.debug("initializeManager: LDAPInterface, LDAPState");

        try {
            // replace standard field in providerUrl and baseDn: %D, %u, %d,  %s, %e, %principal, psRoot
            expandSearchAndBaseDn(principal);

            // Create Ldap Interface
            LdapManagerInterface li = LDAPManagerFactory.createLdapInterface(getLdapInterfaceClassName());
            li.init(providerUrl, baseDn, ldapUser, ldapPass, followReferral, connectionPooling, contactDAO);
            li.setTimeZone(serverTimeZone);
            // set ldapinterface.entryFilter

            logger.info("createSyncSource[new LDAPState]");
            ldapState = new LDAPState(li, dbName);

            if (ldapState != null) {
                ret = true;
            }
        } catch (SyncSourceException e) {
            logger.warn("Ldapinterface error:", e);
            e.printStackTrace();
        } catch (LDAPAccessException e) {
            logger.warn("LDAPInterface somehow failed, please check your settings", e);
        }

        return ret;
    }

    /**
     * @param List <syncItem> syncItems
     * @return
     */
    private SyncItemKey[] extractKeys(List<SyncItem> syncItems) {
        logger.info("extractKeys(" + syncItems.toString() + ")");
        SyncItemKey[] keys = new SyncItemKey[syncItems.size()];
        /*
          Iterator<SyncItem> it = syncItems.iterator();
          int i = 0;
          while( it.hasNext() ) {
          keys[i] = it.next().getKey();
          i++;
          }
         */
        for (int i = 0; i < syncItems.size(); i++) {
            keys[i] = syncItems.get(i).getKey();
        }
        if (logger.isTraceEnabled())
            logger.trace("keys=" + keys.toString());
        return keys;
    }

    /**
     * Returns a timestamp aligned to UTC
     */
    private Timestamp normalizeTimestamp(Timestamp t) {
        return new Timestamp(t.getTime() - getServerTimeZone().getOffset(t.getTime()));
    }

    /**
     * Return the device with the given deviceId
     * @param deviceId String
     * @return Sync4jDevice
     * @throws PersistentStoreException
     */
    private Sync4jDevice getDevice(String deviceId) throws PersistentStoreException {
        logger.info("getDevice()");
        Sync4jDevice device = new Sync4jDevice(deviceId);
        PersistentStore store = Configuration.getConfiguration().getStore();
        store.read(device);
        return device;
    }

    /**
     *
     */
    private void setDeviceInfo(SyncContext context) throws SyncSourceException {
        try {
            Sync4jDevice device = context.getPrincipal().getDevice();
            String timezone = device.getTimeZone();
            if (device.getConvertDate()) {
                if (timezone != null && timezone.length() > 0) {
                    deviceTimeZoneDescription = timezone;
                    setDeviceTimeZone(TimeZone.getTimeZone(deviceTimeZoneDescription));
                }
            }
            setDeviceCharset(device.getCharset());
        } catch (Exception e) {
            throw new SyncSourceException("Error settings the device information." + e.getMessage());
        }
    }

    // ------------------------------------------------------- Abstract methods
    /**
     * Returns a SyncItem with the content of the given file and the given state.
     * @param contacts the contacts to be read
     * @param state the item state
     * @return SyncItem an item with the content of the given file and the given state
     * @throws SyncSourceException if an error occurs
     */
    protected abstract List<SyncItem> getSyncItems(List<Contact> contacts, char state) throws SyncSourceException;

    /**
     * Sets the given syncItem in the given file.
     * @param syncItem the item to set
     * @param contact the contact where to set the item (LDAP Store)
     * @return SyncItem
     * @throws SyncSourceException
     */
    protected abstract SyncItem[] setSyncItems(SyncItem[] syncItem, Contact contact) throws SyncSourceException;

    // rpolli
    /**
     * Sets the given syncItem in the given file.
     * @param syncItem the item to set
     * @param contact the contact where to set the item (LDAP Store)
     * @return SyncItem
     * @throws SyncSourceException
     */
    protected abstract SyncItem createItem(String uid, Contact contact, char state) throws SyncSourceException;
}