org.sipfoundry.sipxconfig.phonebook.PhonebookManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sipfoundry.sipxconfig.phonebook.PhonebookManagerImpl.java

Source

/*
 *
 *
 * Copyright (C) 2007 Pingtel Corp., certain elements licensed under a Contributor Agreement.
 * Contributors retain copyright to elements licensed under a Contributor Agreement.
 * Licensed to the User under the LGPL license.
 *
 *
 */
package org.sipfoundry.sipxconfig.phonebook;

import static java.util.Arrays.asList;
import static java.util.Collections.addAll;
import static org.apache.commons.collections.CollectionUtils.filter;
import static org.apache.commons.collections.CollectionUtils.find;
import static org.apache.commons.collections.CollectionUtils.select;
import static org.apache.commons.lang.StringUtils.join;
import static org.sipfoundry.sipxconfig.common.DaoUtils.checkDuplicates;
import static org.sipfoundry.sipxconfig.common.DaoUtils.requireOneOrZero;
import static org.springframework.dao.support.DataAccessUtils.singleResult;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.classic.Session;
import org.sipfoundry.commons.userdb.profile.UserProfile;
import org.sipfoundry.commons.userdb.profile.UserProfileService;
import org.sipfoundry.sipxconfig.bulk.BulkParser;
import org.sipfoundry.sipxconfig.bulk.csv.CsvWriter;
import org.sipfoundry.sipxconfig.bulk.vcard.VCardParserException;
import org.sipfoundry.sipxconfig.common.CoreContext;
import org.sipfoundry.sipxconfig.common.DataCollectionUtil;
import org.sipfoundry.sipxconfig.common.SipxHibernateDaoSupport;
import org.sipfoundry.sipxconfig.common.User;
import org.sipfoundry.sipxconfig.common.UserException;
import org.sipfoundry.sipxconfig.common.event.DaoEventListener;
import org.sipfoundry.sipxconfig.setting.BeanWithSettingsDao;
import org.sipfoundry.sipxconfig.setting.Group;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;

import com.glaforge.i18n.io.CharsetToolkit;

public class PhonebookManagerImpl extends SipxHibernateDaoSupport<Phonebook>
        implements PhonebookManager, DaoEventListener {
    private static final Log LOG = LogFactory.getLog(PhonebookManagerImpl.class);

    private static final String NAME = "name";
    private static final String FIELD_ID = "id";
    private static final String FIELD_CONTENT = "content";
    private static final String PARAM_USER_ID = "userId";
    private static final String AT_SIGN = "@";

    private static final String QUERY_GROUP = "SELECT u.user_id from Users u "
            + "inner join user_group ug on u.user_id = ug.user_id "
            + "WHERE u.user_type='C' AND ug.group_id=%d ORDER BY u.user_id;";

    private boolean m_phonebookManagementEnabled;
    private String m_externalUsersDirectory;
    private CoreContext m_coreContext;
    private JdbcTemplate m_jdbcTemplate;

    private BulkParser m_csvParser;
    private BulkParser m_vcardParser;
    private String m_vcardEncoding;
    private BeanWithSettingsDao<GeneralPhonebookSettings> m_settingsDao;
    private UserProfileService m_userProfileService;

    public Collection<Phonebook> getPhonebooks() {
        Collection<Phonebook> books = getHibernateTemplate().loadAll(Phonebook.class);
        if (!books.isEmpty()) {
            Collection<Phonebook> privatePhonebooks = new ArrayList<Phonebook>();
            for (Phonebook book : books) {
                if (book.getUser() != null) {
                    privatePhonebooks.add(book);
                }
            }
            if (!privatePhonebooks.isEmpty()) {
                // remove private phone books
                books.removeAll(privatePhonebooks);
            }
        }

        return books;
    }

    public Phonebook getPhonebook(Integer id) {
        Phonebook phonebook = load(Phonebook.class, id);
        return phonebook;
    }

    public void deletePhonebooks(Collection<Integer> ids) {
        for (Integer id : ids) {
            Phonebook phonebook = getPhonebook(id);
            getDaoEventPublisher().publishDelete(phonebook);
            deletePhonebook(phonebook);
        }
    }

    public void deletePhonebook(Phonebook phonebook) {
        getHibernateTemplate().delete(phonebook);
    }

    public void savePhonebook(Phonebook phonebook) {
        checkDuplicates(getHibernateTemplate(), Phonebook.class, phonebook, NAME, new DuplicatePhonebookName());
        getHibernateTemplate().saveOrUpdate(phonebook);
    }

    public PhonebookEntry getPhonebookEntry(Integer id) {
        return getHibernateTemplate().load(PhonebookEntry.class, id);
    }

    public void savePhonebookEntry(PhonebookEntry entry) {
        getHibernateTemplate().saveOrUpdate(entry);
    }

    public void updatePhonebookEntry(PhonebookEntry entry) {
        getHibernateTemplate().merge(entry);
        getDaoEventPublisher().publishSave(entry);
    }

    public void deletePhonebookEntry(PhonebookEntry entry) {
        getHibernateTemplate().delete(entry);
    }

    class DuplicatePhonebookName extends UserException {
        DuplicatePhonebookName() {
            super("&error.duplicatePhonebookName");
        }
    }

    public PhonebookEntry getDuplicatePhonebookEntry(PhonebookEntry newEntry, User user) {
        PhoneEntryComparator entriesComparator = new PhoneEntryComparator();
        Collection<Phonebook> allUserPhonebooks = getAllPhonebooksByUser(user);
        Collection<PhonebookEntry> entries = getEntries(allUserPhonebooks, user);
        for (PhonebookEntry entry : entries) {
            if (entriesComparator.compare(entry, newEntry) == 0) {
                return entry;
            }
        }
        return null;
    }

    @Required
    public void setCoreContext(CoreContext coreContext) {
        m_coreContext = coreContext;
    }

    @Required
    public void setCsvParser(BulkParser csvParser) {
        m_csvParser = csvParser;
    }

    @Required
    public void setVcardParser(BulkParser vcardParser) {
        m_vcardParser = vcardParser;
    }

    @Required
    public void setVcardEncoding(String vcardEncoding) {
        m_vcardEncoding = vcardEncoding;
    }

    /**
     * Where external user lists are kept.
     *
     * This method does not ensure that directory exists.
     */
    public String getExternalUsersDirectory() {
        return m_externalUsersDirectory;
    }

    @Required
    public void setExternalUsersDirectory(String externalUsersDirectory) {
        m_externalUsersDirectory = externalUsersDirectory;
    }

    public Phonebook getPhonebookByName(String name) {
        String query = "phoneBookByName";
        Collection<Phonebook> books = getHibernateTemplate().findByNamedQueryAndNamedParam(query, NAME, name);
        return requireOneOrZero(books, query);
    }

    public Collection<Phonebook> getPublicPhonebooksByUser(User consumer) {
        Collection<Phonebook> books = getHibernateTemplate().findByNamedQueryAndNamedParam("phoneBooksByUser",
                PARAM_USER_ID, consumer.getId());
        return books;
    }

    public Collection<Phonebook> getAllPhonebooksByUser(User consumer) {

        Collection<Phonebook> phonebooks = getPublicPhonebooksByUser(consumer);
        Phonebook privatePhonebook = getPrivatePhonebook(consumer);
        if (privatePhonebook != null) {
            addAll(phonebooks, privatePhonebook);
        }
        return phonebooks;
    }

    public Phonebook getPrivatePhonebook(User user) {
        String query = "privatePhoneBookByUser";
        List<Phonebook> privateBooks = getHibernateTemplate().findByNamedQueryAndNamedParam(query, PARAM_USER_ID,
                user.getId());
        return requireOneOrZero(privateBooks, query);
    }

    public Phonebook getPrivatePhonebookCreateIfRequired(User user) {
        Phonebook phonebook = getPrivatePhonebook(user);
        if (null == phonebook) {
            phonebook = new Phonebook();
            phonebook.setName("privatePhonebook_" + user.getId());
            phonebook.setUser(user);
            savePhonebook(phonebook);
        }

        return phonebook;
    }

    public Collection<PhonebookEntry> getEntries(Collection<Phonebook> phonebooks, User user) {
        Map<String, PhonebookEntry> entries = new TreeMap();
        if (!phonebooks.isEmpty()) {
            for (Phonebook phonebook : phonebooks) {
                for (PhonebookEntry entry : getEntries(phonebook)) {
                    entries.put(getEntryKey(entry), entry);
                }
            }
        }
        boolean everyone = getGeneralPhonebookSettings().isEveryoneEnabled();
        if (everyone) {
            addEveryoneEntries(user, entries);
        }
        return entries.values();
    }

    private Collection<PhonebookEntry> getEntriesConverted(Collection<Phonebook> phonebooks, User user,
            Collection<Phonebook> privatePhonebooks) {
        Map<String, PhonebookEntry> entries = new TreeMap();

        if (!phonebooks.isEmpty()) {
            for (Phonebook phonebook : phonebooks) {
                for (PhonebookEntry entry : getEntries(phonebook)) {
                    PhonebookEntry fileEntry = new PhonebookEntry();
                    fileEntry.setFirstName(entry.getFirstName());
                    fileEntry.setLastName(entry.getLastName());
                    fileEntry.setNumber(entry.getNumber());
                    fileEntry.setAddressBookEntry(entry.getAddressBookEntry());
                    fileEntry.setPhonebook(null);

                    entries.put(getEntryKey(entry), fileEntry);
                }
            }
        }

        if (!privatePhonebooks.isEmpty()) {
            for (Phonebook phonebook : privatePhonebooks) {
                for (PhonebookEntry entry : getEntries(phonebook)) {
                    entries.put(getEntryKey(entry), entry);
                }
            }
        }

        boolean everyone = getGeneralPhonebookSettings().isEveryoneEnabled();
        if (everyone) {
            addEveryoneEntries(user, entries);
        }

        return entries.values();
    }

    private void addEveryoneEntries(final User user, final Map<String, PhonebookEntry> entries) {
        List<UserProfile> allProfiles = m_userProfileService.getAllUserProfiles();
        for (UserProfile profile : allProfiles) {
            if (!user.getUserName().equals(profile.getUserName())) {
                PhonebookEntry entry = extractPhonebookEntry(profile);
                entries.put(getEntryKey(entry), entry);
            }
        }
    }

    private UserPhonebookEntry extractPhonebookEntry(UserProfile userProfile) {
        String firstName = userProfile.getFirstName();
        String lastName = userProfile.getLastName();
        String userName = userProfile.getUserName();
        AddressBookEntry abe = new AddressBookEntry();
        abe.setEmailAddress(userProfile.getEmailAddress());
        abe.setAlternateEmailAddress(userProfile.getAlternateEmailAddress());
        abe.setImId(userProfile.getImId());
        abe.setImDisplayName(userProfile.getImDisplayName());
        abe.setAlternateImId(userProfile.getAlternateImId());
        abe.setJobTitle(userProfile.getJobTitle());
        abe.setJobDept(userProfile.getJobDept());
        abe.setCompanyName(userProfile.getCompanyName());
        abe.setAssistantName(userProfile.getAssistantName());
        abe.setAssistantPhoneNumber(userProfile.getAssistantPhoneNumber());
        abe.setFaxNumber(userProfile.getFaxNumber());
        abe.setLocation(userProfile.getLocation());
        abe.setHomePhoneNumber(userProfile.getHomePhoneNumber());
        abe.setCellPhoneNumber(userProfile.getCellPhoneNumber());
        Address homeAddress = new Address();
        homeAddress.setStreet(userProfile.getHomeAddress().getStreet());
        homeAddress.setCity(userProfile.getHomeAddress().getCity());
        homeAddress.setCountry(userProfile.getHomeAddress().getCountry());
        homeAddress.setState(userProfile.getHomeAddress().getState());
        homeAddress.setZip(userProfile.getHomeAddress().getZip());
        homeAddress.setOfficeDesignation(userProfile.getHomeAddress().getOfficeDesignation());
        abe.setHomeAddress(homeAddress);
        Address jobAddress = new Address();
        jobAddress.setStreet(userProfile.getOfficeAddress().getStreet());
        jobAddress.setCity(userProfile.getOfficeAddress().getCity());
        jobAddress.setCountry(userProfile.getOfficeAddress().getCountry());
        jobAddress.setState(userProfile.getOfficeAddress().getState());
        jobAddress.setZip(userProfile.getOfficeAddress().getZip());
        jobAddress.setOfficeDesignation(userProfile.getOfficeAddress().getOfficeDesignation());
        abe.setOfficeAddress(jobAddress);
        abe.setAvatar(userProfile.getAvatar());
        return new UserPhonebookEntry(firstName, lastName, userName, abe);
    }

    public PagedPhonebook getPagedPhonebook(Collection<Phonebook> phonebook, User user, String startRow,
            String endRow, String queryString) {

        Collection<PhonebookEntry> entries = null;
        Collection<Phonebook> privatePhonebooks = new ArrayList<Phonebook>();

        // add private phonebook
        Boolean showOnPhone = null;
        Phonebook privatePhonebook = getPrivatePhonebook(user);
        if (privatePhonebook != null) {
            privatePhonebooks.add(privatePhonebook);
            showOnPhone = privatePhonebook.getShowOnPhone();
        }

        entries = getEntriesConverted(phonebook, user, privatePhonebooks);

        int totalSize = entries.size();
        if (!StringUtils.isEmpty(queryString) && !queryString.equals("null")) {
            filter(entries, new PhonebookEntryPredicate(queryString));
        }

        Collections.sort(new LinkedList(entries), new PhoneEntryComparator());
        return new PagedPhonebook(entries, totalSize, startRow, endRow, showOnPhone,
                getGoogleDomain().getDomainName());
    }

    public Collection<PhonebookEntry> getEntries(int phonebookId) {
        return getEntries(getPhonebook(phonebookId));
    }

    public Collection<PhonebookEntry> getEntries(Phonebook phonebook) {
        final Map<String, PhonebookEntry> entries = new TreeMap<String, PhonebookEntry>();
        Collection<Group> members = phonebook.getMembers();

        if (members != null) {
            for (Group group : members) {
                m_jdbcTemplate.query(String.format(QUERY_GROUP, group.getId()), new RowCallbackHandler() {
                    @Override
                    public void processRow(ResultSet rs) throws SQLException {
                        int userId = rs.getInt("user_id");
                        UserProfile profile = m_userProfileService.getUserProfile(String.valueOf(userId));
                        if (profile != null) {
                            PhonebookEntry entry = extractPhonebookEntry(profile);
                            entries.put(getEntryKey(entry), entry);
                        }
                    }
                });
            }
        }

        for (PhonebookEntry fileEntry : phonebook.getEntries()) {
            entries.put(getEntryKey(fileEntry), fileEntry);
        }

        List<PhonebookEntry> finalList = new ArrayList(entries.values());
        Collections.sort(finalList, new PhoneEntryComparator());
        return finalList;
    }

    public Collection<PhonebookEntry> getAllEntries(int phonebookId) {
        Map<String, PhonebookEntry> entriesMap = new TreeMap<String, PhonebookEntry>();
        Collection<PhonebookEntry> entries = getEntries(getPhonebook(phonebookId));

        for (PhonebookEntry entry : entries) {
            entriesMap.put(getEntryKey(entry), entry);

        }
        boolean everyone = getGeneralPhonebookSettings().isEveryoneEnabled();
        if (everyone) {
            addEveryoneEntries(entriesMap);
        }

        List<PhonebookEntry> finalList = new ArrayList(entriesMap.values());
        Collections.sort(finalList, new PhoneEntryComparator());
        return finalList;
    }

    private void addEveryoneEntries(final Map<String, PhonebookEntry> entries) {
        List<UserProfile> allProfiles = m_userProfileService.getAllUserProfiles();
        for (UserProfile profile : allProfiles) {
            PhonebookEntry entry = extractPhonebookEntry(profile);
            entries.put(getEntryKey(entry), entry);
        }
    }

    private void addEntriesFromFile(Map<String, PhonebookEntry> entries, InputStream in, String encoding,
            BulkParser parser, boolean extractHeader) throws IOException {
        Reader fileReader = new InputStreamReader(in, encoding);
        parser.parse(fileReader, new PhonebookEntryMaker(entries, extractHeader));
    }

    /**
     * Search the specified phonebooks for all entries that match the given query string. Both the
     * first and last name of each entry are searched using a prefix search (R, Rob, and Robert
     * all match the first name "Robert"). Additionally, for all entries that coorespond to User
     * objects, the aliases of that user object will be searched.
     *
     * @param queryString The string to search for. Can not be null
     */
    public Collection<PhonebookEntry> search(Collection<Phonebook> phonebooks, String queryString,
            User portalUser) {
        RAMDirectory index = new RAMDirectory();
        Collection<PhonebookEntry> phonebookEntries = getEntries(phonebooks, portalUser);

        Map<String, PhonebookEntry> usersToEntries = new HashMap<String, PhonebookEntry>();
        try {
            IndexWriter indexWriter = new IndexWriter(index,
                    new StandardAnalyzer(Version.LUCENE_30, new HashSet<String>()), true,
                    IndexWriter.MaxFieldLength.LIMITED);
            for (PhonebookEntry entry : phonebookEntries) {
                Document doc = null;
                User user = getUserForEntry(entry);
                if (user != null) {
                    doc = documentFromUser(user);
                } else {
                    doc = documentFromPhonebookEntry(entry);
                }

                usersToEntries.put(doc.get(FIELD_ID), entry);
                indexWriter.addDocument(doc);
            }

            indexWriter.optimize();
            indexWriter.close();

            Searcher searcher = new IndexSearcher(index);
            List<Document> searchResults = doSearch(searcher, queryString);
            List<PhonebookEntry> entryResults = new ArrayList<PhonebookEntry>();
            for (Document doc : searchResults) {
                entryResults.add(usersToEntries.get(doc.get(FIELD_ID)));
            }

            return entryResults;
        } catch (IOException ioe) {
            throw new UserException(ioe);
        } catch (ParseException pe) {
            throw new UserException(pe);
        }
    }

    private User getUserForEntry(PhonebookEntry entry) {
        return m_coreContext.loadUserByUserNameOrAlias(entry.getNumber());
    }

    /*
     * @param queryString A case-insensitive query string
     */
    private List<Document> doSearch(Searcher searcher, String queryString) throws IOException, ParseException {
        Term searchTerm = new Term(FIELD_CONTENT, queryString.toLowerCase());
        Query query = new PrefixQuery(searchTerm);
        // max number of returned docs set to 20000
        TopDocs topDocs = searcher.search(query, 20000);
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Document> docs = new ArrayList<Document>();
        for (int i = 0; i < scoreDocs.length; i++) {
            int docId = scoreDocs[i].doc;
            docs.add(searcher.doc(docId));
        }

        return docs;
    }

    private Document documentFromUser(User user) {
        Document doc = new Document();
        addIdToDoc(doc, user.getName());
        addTextFieldToDocument(doc, user.getUserName());
        addTextFieldToDocument(doc, user.getFirstName());
        addTextFieldToDocument(doc, user.getLastName());
        addTextFieldToDocument(doc, user.getAliasesString());

        return doc;
    }

    private Document documentFromPhonebookEntry(PhonebookEntry entry) {
        Document doc = new Document();
        addIdToDoc(doc, String.valueOf(entry.getId()));
        addTextFieldToDocument(doc, entry.getNumber());
        addTextFieldToDocument(doc, entry.getFirstName());
        addTextFieldToDocument(doc, entry.getLastName());

        return doc;
    }

    private void addIdToDoc(Document doc, String id) {
        doc.add(new Field(FIELD_ID, id, Field.Store.YES, Field.Index.NOT_ANALYZED));
    }

    private void addTextFieldToDocument(Document doc, String value) {
        if (value != null) {
            doc.add(new Field(FIELD_CONTENT, value, Field.Store.NO, Field.Index.ANALYZED));
        }
    }

    public void reset() {
        for (Phonebook phonebook : getPhonebooks()) {
            deletePhonebook(phonebook);
        }
    }

    static class PhoneEntryComparator implements Comparator<PhonebookEntry> {
        public int compare(PhonebookEntry a, PhonebookEntry b) {
            CompareToBuilder compare = new CompareToBuilder();
            compare.append(a.getLastName(), b.getLastName());
            compare.append(a.getFirstName(), b.getFirstName());
            compare.append(a.getNumber(), b.getNumber());
            return compare.toComparison();
        }
    }

    static class PhonebookEntryPredicate implements Predicate {

        private final String m_queryString;

        public PhonebookEntryPredicate(String queryString) {
            m_queryString = queryString;
        }

        @Override
        public boolean evaluate(Object phoneEntry) {
            if (phoneEntry instanceof PhonebookEntry) {
                PhonebookEntry entry = (PhonebookEntry) phoneEntry;
                if (StringUtils.containsIgnoreCase(entry.getFirstName(), m_queryString)
                        || StringUtils.containsIgnoreCase(entry.getLastName(), m_queryString)
                        || StringUtils.containsIgnoreCase(entry.getNumber(), m_queryString)
                        || (entry.getAddressBookEntry() != null && StringUtils.containsIgnoreCase(
                                entry.getAddressBookEntry().getEmailAddress(), m_queryString))) {
                    return true;
                }
            }
            return false;
        }

    }

    static class FileEntrySearchPredicate implements Predicate {

        private final String m_internalId;

        public FileEntrySearchPredicate(String internalId) {
            m_internalId = internalId;
        }

        @Override
        public boolean evaluate(Object phoneEntry) {
            if (phoneEntry instanceof FilePhonebookEntry) {
                FilePhonebookEntry entry = (FilePhonebookEntry) phoneEntry;
                return StringUtils.containsIgnoreCase(entry.getInternalId(), m_internalId);
            }

            return false;
        }

    }

    static class GoogleEntrySearchPredicate implements Predicate {

        private final String m_account;

        public GoogleEntrySearchPredicate(String account) {
            m_account = account;
        }

        @Override
        public boolean evaluate(Object phoneEntry) {
            if (phoneEntry instanceof GooglePhonebookEntry) {
                GooglePhonebookEntry entry = (GooglePhonebookEntry) phoneEntry;
                return StringUtils.containsIgnoreCase(entry.getGoogleAccount(), m_account);
            }
            return false;
        }

    }

    static class PhonebookEntryMaker implements org.apache.commons.collections.Closure {
        private final Map<String, PhonebookEntry> m_entries;
        private PhonebookFileEntryHelper m_header = new InternalPhonebookVcardHeader();
        private boolean m_extractHeader;

        PhonebookEntryMaker(Map entries, boolean extractHeader) {
            m_entries = entries;
            m_extractHeader = extractHeader;
        }

        public void execute(Object input) {
            if (m_extractHeader) {
                Map<String, Integer> header = extractHeader(input);
                m_header = findHeaderType(header);
                m_extractHeader = false;
            } else {
                String[] row = (String[]) input;
                PhonebookEntry entry = new StringArrayPhonebookEntry(m_header, row);
                m_entries.put(getEntryKey(entry), entry);
            }
        }

        /**
         * Attempt to guess is this is Outlook of GMail generated CSV file.
         *
         * It's based on looking for specific words in header line ('yomi' for GMail and 'tty/tdd
         * phone' for Outlook. Would be nice to have a better method for that.
         *
         */
        private PhonebookFileEntryHelper findHeaderType(Map<String, Integer> header) {
            if (header == null) {
                return new InternalPhonebookCsvHeader();
            }

            // searching for a "yomi" word in the header to see if it is a gmail header
            // or searching for a "tty/tdd phone" word in the header to see if it is an
            // outlook header
            Set<String> keySet = header.keySet();
            for (String key : keySet) {
                if (key.contains("\n")) {
                    throw new InvalidPhonebookFormat();
                }
                if (key.toLowerCase().contains("yomi")) {
                    return new GooglePhonebookCsvHeader(header);
                }
                if (key.toLowerCase().contains("tty/tdd phone")) {
                    return new OutlookPhonebookCsvHeader(header);
                }
            }
            return new InternalPhonebookCsvHeader();
        }

        private Map<String, Integer> extractHeader(Object input) {
            String[] row = (String[]) input;
            Map<String, Integer> header = new HashMap<String, Integer>();
            for (int i = 0; i < row.length; i++) {
                header.put(row[i], i);
            }
            return header;
        }
    }

    /**
     * public so that it works with Velocity
     */
    public static class StringArrayPhonebookEntry extends PhonebookEntry {
        private final String[] m_row;
        private final PhonebookFileEntryHelper m_helper;

        StringArrayPhonebookEntry(String... row) {
            this(new InternalPhonebookCsvHeader(), row);
        }

        StringArrayPhonebookEntry(PhonebookFileEntryHelper helper, String... row) {
            if (row.length < 3) {
                throw new InvalidPhonebookFormat();
            }
            m_row = row;
            m_helper = helper;
        }

        @Override
        public String getFirstName() {
            return m_helper.getFirstName(m_row);
        }

        @Override
        public String getLastName() {
            return m_helper.getLastName(m_row);
        }

        @Override
        public String getNumber() {
            return m_helper.getNumber(m_row);
        }

        @Override
        public AddressBookEntry getAddressBookEntry() {
            return m_helper.getAddressBookEntry(m_row);
        }
    }

    private static class InvalidPhonebookFormat extends UserException {
        public InvalidPhonebookFormat() {
            super("&msg.invalidPhonebookFormat");
        }
    }

    /**
     * public so Velocity doesn't reject object
     */
    public static class UserPhonebookEntry extends PhonebookEntry {
        private String m_firstName;
        private String m_lastName;
        private String m_number;
        private AddressBookEntry m_abe;

        UserPhonebookEntry(String first, String last, String number, AddressBookEntry abe) {
            m_firstName = first;
            m_lastName = last;
            m_number = number;
            m_abe = abe;
        }

        @Override
        public String getFirstName() {
            return m_firstName;
        }

        @Override
        public String getLastName() {
            return m_lastName;
        }

        @Override
        public String getNumber() {
            return m_number;
        }

        @Override
        public AddressBookEntry getAddressBookEntry() {
            return m_abe;
        }
    }

    public void onDelete(Object entity) {
        if (entity instanceof Group) {
            Group group = (Group) entity;
            getHibernateTemplate().update(group);
            if (User.GROUP_RESOURCE_ID.equals(group.getResource())) {
                for (Phonebook book : getPhonebooks()) {
                    DataCollectionUtil.removeByPrimaryKey(book.getConsumers(), group.getPrimaryKey());
                    DataCollectionUtil.removeByPrimaryKey(book.getMembers(), group.getPrimaryKey());
                    savePhonebook(book);
                }
            }
        } else if (entity instanceof User) {
            User user = (User) entity;
            removePrivatePhonebook(user);
        }
    }

    public void onSave(Object entity) {
    }

    public boolean getPhonebookManagementEnabled() {
        return m_phonebookManagementEnabled;
    }

    public void setPhonebookManagementEnabled(boolean phonebookManagementEnabled) {
        m_phonebookManagementEnabled = phonebookManagementEnabled;
    }

    public void exportPhonebook(Collection<PhonebookEntry> entries, OutputStream out, PhonebookFormat format)
            throws IOException {
        if (entries.isEmpty()) {
            throw new UserException("&error.phonebookEmpty");
        }
        Writer writer = new OutputStreamWriter(out, m_vcardEncoding);
        PhonebookWriter pbWriter;
        if (format == PhonebookFormat.CSV) {
            pbWriter = new CsvWriter(writer, true, PhonebookEntry.labels());
        } else {
            pbWriter = new VcardWriter(writer);
        }
        for (PhonebookEntry entry : entries) {
            pbWriter.write(entry);
        }
        writer.flush();
    }

    @Override
    public int addEntriesFromFile(Integer phonebookId, InputStream is) {
        try {
            Phonebook phonebook = getPhonebook(phonebookId);
            int count = addEntries(phonebook, is);
            savePhonebook(phonebook);
            return count;
        } catch (IOException e) {
            throw new VCardParserException();
        }
    }

    @Override
    public int addEntriesFromGoogleAccount(Integer phonebookId, String account, String password) {
        Phonebook phonebook = getPhonebook(phonebookId);
        deleteGoogleImportedEntries(account, phonebook);
        String userAccount = new String(account);
        if (!StringUtils.contains(account, AT_SIGN)) {
            userAccount = userAccount + AT_SIGN + getGoogleDomain().getDomainName();
        }

        GoogleImporter googleImporter = new GoogleImporter(userAccount, password);
        int count = googleImporter.addEntries(phonebook);
        savePhonebook(phonebook);
        return count;
    }

    private void deleteGoogleImportedEntries(String account, Phonebook phonebook) {
        Collection existingEntries = select(phonebook.getEntries(), new GoogleEntrySearchPredicate(account));
        phonebook.getEntries().removeAll(existingEntries);

    }

    int addEntries(Phonebook phonebook, InputStream is) throws IOException {
        Map<String, PhonebookEntry> entries = new TreeMap<String, PhonebookEntry>();

        BufferedInputStream in = new BufferedInputStream(is);
        String encoding = getEncoding(in);

        if (isVcard(in, encoding)) {
            addEntriesFromFile(entries, in, encoding, m_vcardParser, false);
        } else {
            addEntriesFromFile(entries, in, encoding, m_csvParser, true);
        }

        List<PhonebookEntry> entriesList = new ArrayList(entries.values());
        for (PhonebookEntry entry : entriesList) {
            PhonebookEntry fileEntry = new FilePhonebookEntry();

            fileEntry.setFirstName(entry.getFirstName());
            fileEntry.setLastName(entry.getLastName());
            fileEntry.setNumber(entry.getNumber());
            fileEntry.setAddressBookEntry(entry.getAddressBookEntry());
            fileEntry.setPhonebook(phonebook);
            String uniqueKey = getEntryKey(fileEntry);
            fileEntry.setInternalId(uniqueKey);

            PhonebookEntry oldEntry = (PhonebookEntry) find(phonebook.getEntries(),
                    new FileEntrySearchPredicate(uniqueKey));
            phonebook.getEntries().remove(oldEntry);

            phonebook.addEntry(fileEntry);
        }
        return entriesList.size();
    }

    @SuppressWarnings("deprecation")
    public void removeTableColumns() {
        try {
            m_jdbcTemplate.execute("alter table phonebook drop column members_csv_filename");
            m_jdbcTemplate.execute("alter table phonebook drop column members_vcard_filename");
            LOG.info("Columns members_csv_filename and members_vcard_filename were removed from phonebook table.");
        } catch (DataAccessException e) {
            LOG.error("failed to remove columns members_csv_filename and members_vcard_filename" + e.getMessage());
        }

    }

    /**
     * Retrieve the name of file in which phone book entries were kept
     *
     * This method is only used during upgrade: in 4.0 sipXconfig didn't keep list of phonebook
     * entries in DB. It only kept references to external files. Every time phonebook was
     * generated files were parsed. During upgrade to 4.2 files are parsed, entries are stored in
     * DB and later files are discarded.
     *
     * Since Phonebook object does not have fields for storing filenames any more we need to run
     * direct SQL query to retrieve them.
     *
     */
    public Map<Integer, String[]> getPhonebookFilesName() {
        Map<Integer, String[]> names = new TreeMap<Integer, String[]>();
        try {
            String query = "select phonebook_id, members_csv_filename, members_vcard_filename from phonebook;";
            Session currentSession = getHibernateTemplate().getSessionFactory().getCurrentSession();
            List<Object[]> entries = currentSession.createSQLQuery(query)
                    .addScalar("phonebook_id", Hibernate.INTEGER)
                    .addScalar("members_csv_filename", Hibernate.STRING)
                    .addScalar("members_vcard_filename", Hibernate.STRING).list();
            for (Object[] entry : entries) {
                String[] files = { (String) entry[1], (String) entry[2] };
                names.put((Integer) entry[0], files);
            }
            LOG.info("Extracted files names from " + names.size() + " phonebooks.");
        } catch (HibernateException e) {
            LOG.warn(e.getMessage());
        }
        return names;
    }

    /**
     * Trying to determine if the file is vCard file
     */
    boolean isVcard(BufferedInputStream is, String encoding) {
        final String vcardSignature = "BEGIN:VCARD";
        try {
            // keep buffer smaller than the readlimit: trying to ensure that we can reset the
            // stream
            BufferedReader isr = new BufferedReader(new InputStreamReader(is, encoding),
                    vcardSignature.length() * 2);
            is.mark(vcardSignature.length() * 10);
            String line;
            do {
                line = isr.readLine();
            } while (StringUtils.isBlank(line));
            boolean isVcard = vcardSignature.equalsIgnoreCase(line);
            is.reset();
            return isVcard;
        } catch (IOException e) {
            return false;
        }
    }

    private static String getEntryKey(PhonebookEntry entry) {
        return join(asList(entry.getNumber(), entry.getFirstName(), entry.getLastName()), '_');
    }

    public String getEncoding(InputStream is) throws IOException {
        byte[] buffer = new byte[4096];
        is.mark(0);
        is.read(buffer);
        is.reset();

        File tempFile = File.createTempFile("PhonebookFileEntryTemp", null);
        FileOutputStream out = new FileOutputStream(tempFile);
        out.write(buffer);
        out.flush();
        out.close();

        String encoding = CharsetToolkit.guessEncoding(tempFile, buffer.length).displayName();
        tempFile.delete();

        return encoding;
    }

    private Collection<PhonebookEntry> convertPhonebookEntries(Collection<PhonebookEntry> entriesList) {
        Collection<PhonebookEntry> fileEntries = new ArrayList<PhonebookEntry>();
        for (PhonebookEntry entry : entriesList) {
            PhonebookEntry fileEntry = new PhonebookEntry();
            fileEntry.setFirstName(entry.getFirstName());
            fileEntry.setLastName(entry.getLastName());
            fileEntry.setNumber(entry.getNumber());
            fileEntry.setAddressBookEntry(entry.getAddressBookEntry());
            fileEntry.setPhonebook(null);
            fileEntries.add(fileEntry);
        }

        return fileEntries;
    }

    /**
     * To be used only for upgrading existing contacts
     */
    public void updateFilePhonebookEntryInternalIds() {
        Collection<FilePhonebookEntry> fileEntries = getHibernateTemplate().loadAll(FilePhonebookEntry.class);
        for (FilePhonebookEntry entry : fileEntries) {
            entry.setInternalId(getEntryKey(entry));
        }
        getHibernateTemplate().saveOrUpdateAll(fileEntries);
    }

    public GoogleDomain getGoogleDomain() {
        List domains = getHibernateTemplate().loadAll(GoogleDomain.class);
        GoogleDomain gd = (GoogleDomain) singleResult(domains);
        return gd;
    }

    public void saveGoogleDomain(GoogleDomain gd) {
        getHibernateTemplate().saveOrUpdate(gd);
    }

    public void saveGeneralPhonebookSettings(GeneralPhonebookSettings generalPhonebookSettings) {
        m_settingsDao.upsert(generalPhonebookSettings);
    }

    public GeneralPhonebookSettings getGeneralPhonebookSettings() {
        return m_settingsDao.findOrCreateOne();
    }

    public void removePrivatePhonebook(User user) {
        Phonebook privatePhonebook = getPrivatePhonebook(user);
        if (privatePhonebook != null) {
            deletePhonebook(privatePhonebook);
        }
    }

    public void setConfigJdbcTemplate(JdbcTemplate template) {
        m_jdbcTemplate = template;
    }

    @Required
    public void setSettingsDao(BeanWithSettingsDao<GeneralPhonebookSettings> settingsDao) {
        m_settingsDao = settingsDao;
    }

    public void setUserProfileService(UserProfileService profileService) {
        m_userProfileService = profileService;
    }
}