/* * � Copyright IBM Corp. 2014 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.ibm.domino.commons.model; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Vector; import lotus.domino.Database; import lotus.domino.DateTime; import lotus.domino.Directory; import lotus.domino.Document; import lotus.domino.NotesException; import lotus.domino.Session; import lotus.domino.View; import lotus.domino.ViewEntry; import lotus.domino.ViewNavigator; import com.ibm.domino.commons.util.BackendUtil; public class RecentContactsProvider implements IRecentContactsProvider { /** * Provides a service for collecting a list of internet addresses and names of those that have been in most recent * contact with a user. * * This data is derived from the Sent and All Documents views of a user's mailfile. */ private static final String VIEW_ALL_DOCUMENTS = "($All)"; //$NON-NLS-1$ private static final String VIEW_SENT = "($SENT)"; //$NON-NLS-1$ /** * Returns a List of PersonRecords representing a user's most recent contacts based on latest emails either sent or * received. * * @param mailFile * DB to open for scanning email data * @param isSent * <p> * true - recent contacts are based on recipients of user's emails<br /> * false - recent contacts are based on senders to user's mailfile * </p> * @param count * Maximum number of records to return * @param scanLimit * Maximum number of documents to scan * @return */ public RecentContactsResult getRecentContacts(Database database, final boolean isSent, final int scanStart, final int scanLimit) throws ModelException { final String collectionString = isSent ? VIEW_SENT : VIEW_ALL_DOCUMENTS; final Map<String, RecentContact> savedEmails = new LinkedHashMap<String, RecentContact>(); final List<RecentContact> returnList = new LinkedList<RecentContact>(); // This is for debug - currently not printed anywhere @SuppressWarnings("unused")//$NON-NLS-1$ int badNames = 0; int docsScanned = 0; Directory nDirectory = null; View collection = null; try { Session session = database.getParent(); nDirectory = session.getDirectory(); collection = database.getView(collectionString); collection.setAutoUpdate(false); collection.resortView("Date", false); //$NON-NLS-1$ ViewNavigator nav = collection.createViewNav(); ViewEntry entry = nav.getNth(scanStart + 1); while (entry != null && docsScanned < scanLimit) { final Document doc = entry.getDocument(); if ( doc == null || (!isSent && !doc.hasItem("DeliveredDate")) ) { //$NON-NLS-1$ // Ignore sent and draft documents in the all docs view } else { // Get a list of recent contacts from the note final List<RecentContact> recentContacts = isSent ? parseSentToFromNote(doc) : parseReceivedFromNote(doc); // Clean up and filter results for (final RecentContact person : recentContacts) { person.DisplayName = person.DisplayName.trim().replaceAll("\"|'", ""); //$NON-NLS-1$ person.InternetAddress = person.InternetAddress.trim().replaceAll("\"|'", ""); //$NON-NLS-1$ final RecentContact previousPersonRecord = savedEmails.get(person.InternetAddress.toLowerCase()); if (previousPersonRecord != null) { ++previousPersonRecord.frequency; // If the current record has a real display name and the old one doesn't - update it if (previousPersonRecord.displayNameEqualsEmail && !person.displayNameEqualsEmail) { previousPersonRecord.DisplayName = person.DisplayName; } } else { try { final boolean invalidEmailAddress = (person.InternetAddress.startsWith("CN=") //$NON-NLS-1$ || !person.InternetAddress.contains("@") || person.InternetAddress.contains("/") || person.DisplayName.length() == 0 || person.InternetAddress.length() == 0 || person.InternetAddress.indexOf('@') != person.InternetAddress.lastIndexOf('@')); if (nDirectory == null && invalidEmailAddress) { // Log some error here, maybe? } else if (invalidEmailAddress) { final String oldInet = person.InternetAddress; final String notesName = oldInet.replaceAll("CN=|OU=|O=|@.*", ""); //$NON-NLS-1$ final Vector address_information = nDirectory.getMailInfo(notesName); // Inet address is second to last item final String inet = (String) address_information.get(address_information.size() - 2); person.InternetAddress = inet; person.DisplayName = notesName; // We do this to avoid processing so that the initial hashcheck will increment // the person record without coming down into this logic savedEmails.put(oldInet, person); if (savedEmails.containsKey(inet.toLowerCase())) { final RecentContact storedPerson = savedEmails.get(inet.toLowerCase()); ++storedPerson.frequency; continue; } } returnList.add(person); savedEmails.put(person.InternetAddress.toLowerCase(), person); } catch (final NotesException e) { // Name wasn't found - logging for debug ++badNames; } } } } ViewEntry next = nav.getNext(); BackendUtil.safeRecycle(doc); entry.recycle(); entry = next; ++docsScanned; } } catch (final NotesException e) { throw new ModelException("Unexpected error in recent contacts provider", e); // $NLX-RecentContactsProvider.Unexpectederrorinrecentcontactspr-1$ } finally { BackendUtil.safeRecycle(collection); BackendUtil.safeRecycle(nDirectory); } return new RecentContactsResult(returnList, docsScanned); } /** * Given a note that was sent to other users - parse display names and internet addresses. List of PersonRecords will * be returned for data parsed from TO, CC, and BCC fields. * * @param doc * Email note to parse for recipient data * @return * @throws NotesException */ private List<RecentContact> parseSentToFromNote(final Document doc) throws NotesException { final List<RecentContact> recentContacts = new LinkedList<RecentContact>(); final Vector dateVec = doc.getItemValue("PostedDate"); //$NON-NLS-1$ final Date emailDate = getDateFromVector(dateVec); final String[][] itemPairsToRead = { { "InetSendTo", "SendTo" }, //$NON-NLS-1$ //$NON-NLS-2$ { "InetCopyTo", "CopyTo" }, //$NON-NLS-1$ //$NON-NLS-2$ { "InetBlindCopyTo", "BlindCopyTo" } }; //$NON-NLS-1$ //$NON-NLS-2$ for (int i = 0; i < itemPairsToRead.length; ++i) { final Vector inetVector = doc.getItemValue(itemPairsToRead[i][0]); final Vector nameVector = doc.getItemValue(itemPairsToRead[i][1]); if (nameVector == null || nameVector.size() == 0) { continue; } final boolean hasInet = (inetVector != null && inetVector.size() > 0); for (int addrIndex = 0; addrIndex < nameVector.size(); ++addrIndex) { final RecentContact person = new RecentContact(); if (hasInet && !(person.InternetAddress = (String) inetVector.get(addrIndex)).contains("<")) { person.DisplayName = (String) nameVector.get(addrIndex); person.DisplayName = person.DisplayName.replaceAll("CN=|OU=|O=|@.*", ""); //$NON-NLS-1$ } else { // This is not a notes recipient then - address in SendTo processComboInet(person, (String) nameVector.get(addrIndex)); } person.lastContacted = emailDate; recentContacts.add(person); } } return recentContacts; } /** * Given a note received from another user, parse the from display name and internet address. * * @param doc * Email note to parse for sender data * @return * @throws NotesException */ private List<RecentContact> parseReceivedFromNote(final Document doc) throws NotesException { // Currently only getting from address - may get items like others that were on the email // in the future. final List<RecentContact> recentContacts = new ArrayList<RecentContact>(1); final RecentContact sender = new RecentContact(); recentContacts.add(sender); final Vector dateVec = doc.getItemValue("DeliveredDate"); //$NON-NLS-1$ if (doc.hasItem("INetFrom")) //$NON-NLS-1$ { sender.InternetAddress = doc.getItemValueString("INetFrom"); //$NON-NLS-1$ if (sender.InternetAddress.contains("<")) { processComboInet(sender, sender.InternetAddress); } else { sender.DisplayName = doc.getItemValueString("From"); //$NON-NLS-1$ sender.DisplayName = sender.DisplayName.replaceAll("CN=|OU=|O=|@.*", ""); //$NON-NLS-1$ } } else { processComboInet(sender, doc.getItemValueString("From")); //$NON-NLS-1$ } sender.lastContacted = getDateFromVector(dateVec); return recentContacts; } /** * Parse display name and internet address from a string in the format of * * DisplayName <Interent@Addr.ess> * * @param person * PersonRecord to modify InternetAddress and DisplayName * @param comboInet * String which potentially contains an address in the format of DisplayName <Interent@Addr.ess> */ private void processComboInet(final RecentContact person, final String comboInet) { final int startOfBracket = comboInet.indexOf("<"); if (startOfBracket < 0) { // We have no display name - just email person.InternetAddress = comboInet.replaceAll("<|>", ""); //$NON-NLS-1$ person.DisplayName = person.InternetAddress; person.displayNameEqualsEmail = true; } else { // Extract the display and internet addresses person.DisplayName = comboInet.substring(0, startOfBracket); person.InternetAddress = comboInet.substring(startOfBracket + 1, comboInet.length() - 1); } } /** * Get Java Date from Notes item value (vector) * * @param dateVec * Vector to process into date * @return */ private Date getDateFromVector(final Vector dateVec) { if (dateVec.size() == 0) { return new Date(); } else { final DateTime dateTime = (DateTime) dateVec.get(0); final Date returnDate = safeToJavaDate(dateTime); return (returnDate != null) ? returnDate : new Date(); } } /** * Converts a Notes DateTime to a Java Date. We have problems with date fields with values of FFFFFFFF:FFFFFFFFF * since they cause toJavaDate() to choke; we'll catch it here and just return a null. * * Borrowed/Adapted - from Traveler */ private Date safeToJavaDate(final DateTime date) { Date value = null; try { final String time = date.getTimeOnly(); if ((time == null) || time.equals("")) { DateTime workingDate = date.getParent().createDateTime(date.getDateOnly() + " 00:00:00 GMT"); //$NON-NLS-1$ value = workingDate.toJavaDate(); workingDate.recycle(); workingDate = null; } else { value = date.toJavaDate(); } } catch (final NotesException notesEx) { // If there was an error we'll just return a null } return value; } }