/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.plugin.addrbook.msoutlook;
import java.util.*;
import java.util.regex.*;
import net.java.sip.communicator.plugin.addrbook.*;
import net.java.sip.communicator.service.contactsource.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;
/**
* Implements <tt>ContactQuery</tt> for the Address Book of Microsoft Outlook.
*
* @author Lyubomir Marinov
* @author Vincent Lucas
*/
public class MsOutlookAddrBookContactQuery
extends AbstractAddrBookContactQuery<MsOutlookAddrBookContactSourceService>
{
/**
* The <tt>Logger</tt> used by the <tt>MsOutlookAddrBookContactQuery</tt>
* class and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(MsOutlookAddrBookContactQuery.class);
public static final int dispidEmail1EmailAddress = 12;
public static final int dispidEmail2EmailAddress = 13;
public static final int dispidEmail3EmailAddress = 14;
/**
* The object type of a <tt>SourceContact</tt> in the Address Book of
* Microsoft Outlook.
*/
private static final long MAPI_MAILUSER = 0x00000006;
/**
* The IDs of the properties of <tt>MAPI_MAILUSER</tt> which are to be
* queried by the <tt>MsOutlookAddrBookContactQuery</tt> instances.
*/
public static final long[] MAPI_MAILUSER_PROP_IDS
= new long[]
{
0x3001 /* PR_DISPLAY_NAME */,
0x3003 /* PR_EMAIL_ADDRESS */,
0x3A06 /* PR_GIVEN_NAME */,
0x3A44 /* PR_MIDDLE_NAME */,
0x3A11 /* PR_SURNAME */,
0x3A08 /* PR_BUSINESS_TELEPHONE_NUMBER */,
0x3A1B /* PR_BUSINESS2_TELEPHONE_NUMBER */,
0x3A09 /* PR_HOME_TELEPHONE_NUMBER */,
0x3A2F /* PR_HOME2_TELEPHONE_NUMBER */,
0x3A1C /* PR_MOBILE_TELEPHONE_NUMBER */,
0x3A1F /* PR_OTHER_TELEPHONE_NUMBER */,
0x0FFE /* PR_OBJECT_TYPE */,
0x00008084 /* dispidEmail1OriginalDisplayName */,
0x00008094 /* dispidEmail2OriginalDisplayName */,
0x000080A4 /* dispidEmail3OriginalDisplayName */,
0x3A16 /* PR_COMPANY_NAME */,
0x0FFF /* PR_ORIGINAL_ENTRYID */,
0x3A24 /* dispidFax1EmailAddress */,
0x3A25 /* dispidFax2EmailAddress */,
0x3A23 /* dispidFax3EmailAddress */,
0x3A4F /* PR_NICKNAME */,
0x3A45 /* PR_DISPLAY_NAME_PREFIX */,
0x3A50 /* PR_PERSONAL_HOME_PAGE */,
0x3A51 /* PR_BUSINESS_HOME_PAGE */,
0x3A17 /* PR_TITLE */,
0x00008062 /* dispidInstMsg */,
0x00008046, // PR_BUSINESS_ADDRESS_CITY
0x00008049, // PR_BUSINESS_ADDRESS_COUNTRY
0x00008048, // PR_BUSINESS_ADDRESS_POSTAL_CODE
0x00008047, // PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE
0x00008045, // PR_BUSINESS_ADDRESS_STREET
0x3A59, // PR_HOME_ADDRESS_CITY
0x3A5A, // PR_HOME_ADDRESS_COUNTRY
0x3A5B, // PR_HOME_ADDRESS_POSTAL_CODE
0x3A5C, // PR_HOME_ADDRESS_STATE_OR_PROVINCE
0x3A5D, // PR_HOME_ADDRESS_STREET
0x0000801A, // dispidHomeAddress
0x0000801B // dispidWorkAddress
};
/**
* The object type of a <tt>SourceContact</tt> in a Contacts folder of
* Microsoft Outlook.
*/
private static final long MAPI_MESSAGE = 0x00000005;
/**
* The flag which signals that MAPI strings should be returned in the
* Unicode character set.
*/
public static final long MAPI_UNICODE = 0x80000000;
/**
* The id of the <tt>PR_ATTACHMENT_CONTACTPHOTO</tt> MAPI property.
*/
public static final long PR_ATTACHMENT_CONTACTPHOTO = 0x7FFF;
/**
* The index of the id of the <tt>PR_BUSINESS_TELEPHONE_NUMBER</tt> property
* in {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_BUSINESS_TELEPHONE_NUMBER = 5;
/**
* The index of the id of the <tt>PR_BUSINESS2_TELEPHONE_NUMBER</tt>
* property in {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_BUSINESS2_TELEPHONE_NUMBER = 6;
public static final int PR_COMPANY_NAME = 15;
/**
* The index of the id of the <tt>PR_DISPLAY_NAME</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_DISPLAY_NAME = 0;
/**
* The index of the id of the <tt>PR_EMAIL_ADDRESS</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_EMAIL_ADDRESS = 1;
/**
* The index of the id of the <tt>PR_GIVEN_NAME</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_GIVEN_NAME = 2;
/**
* The index of the id of the <tt>PR_HOME_TELEPHONE_NUMBER</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_HOME_TELEPHONE_NUMBER = 7;
/**
* The index of the id of the <tt>PR_HOME2_TELEPHONE_NUMBER</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_HOME2_TELEPHONE_NUMBER = 8;
/**
* The index of the id of the <tt>PR_MIDDLE_NAME</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_MIDDLE_NAME = 3;
/**
* The index of the id of the <tt>PR_MOBILE_TELEPHONE_NUMBER</tt> property
* in {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_MOBILE_TELEPHONE_NUMBER = 9;
/**
* The index of the id of the <tt>PR_OTHER_TELEPHONE_NUMBER</tt> property
* in {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_OTHER_TELEPHONE_NUMBER = 10;
/**
* The index of the id of the <tt>PR_OBJECT_TYPE</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_OBJECT_TYPE = 11;
/**
* The index of the id of the <tt>PR_SURNAME</tt> property in
* {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_SURNAME = 4;
/**
* The index of the id of the <tt>PR_ORIGINAL_ENTRYID</tt> property
* in {@link #MAPI_MAILUSER_PROP_IDS}.
*/
public static final int PR_ORIGINAL_ENTRYID = 16;
/**
* The index of the 1st fax telephone number (business fax).
*/
public static final int dispidFax1EmailAddress = 17;
/**
* The index of the 2nd fax telephone number (home fax).
*/
public static final int dispidFax2EmailAddress = 18;
/**
* The index of the 3rd fax telephone number (other fax).
*/
public static final int dispidFax3EmailAddress = 19;
/**
* The index of the nickname.
*/
public static final int PR_NICKNAME = 20;
/**
* The index of the name prefix.
*/
public static final int PR_DISPLAY_NAME_PREFIX = 21;
/**
* The index of the personnal home page
*/
public static final int PR_PERSONAL_HOME_PAGE = 22;
/**
* The index of the business home page
*/
public static final int PR_BUSINESS_HOME_PAGE = 23;
/**
* The index of the job title.
*/
public static final int PR_TITLE = 24;
/**
* The index of the instant messaging address.
*/
public static final int dispidInstMsg = 25;
/**
* The index of the business city of the postal address.
*/
public static final int PR_BUSINESS_ADDRESS_CITY = 26;
/**
* The index of the business country of the postal address.
*/
public static final int PR_BUSINESS_ADDRESS_COUNTRY = 27;
/**
* The index of the business postal code of the postal address.
*/
public static final int PR_BUSINESS_ADDRESS_POSTAL_CODE = 28;
/**
* The index of the business state or province of the postal address.
*/
public static final int PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE = 29;
/**
* The index of the business street of the postal address.
*/
public static final int PR_BUSINESS_ADDRESS_STREET = 30;
/**
* The index of the home city of the postal address.
*/
public static final int PR_HOME_ADDRESS_CITY = 31;
/**
* The index of the home country of the postal address.
*/
public static final int PR_HOME_ADDRESS_COUNTRY = 32;
/**
* The index of the home postal code of the postal address.
*/
public static final int PR_HOME_ADDRESS_POSTAL_CODE = 33;
/**
* The index of the home state or province of the postal address.
*/
public static final int PR_HOME_ADDRESS_STATE_OR_PROVINCE = 34;
/**
* The index of the home street of the postal address.
*/
public static final int PR_HOME_ADDRESS_STREET = 35;
/**
* The index of the display for the home postal address.
*/
public static final int dispidHomeAddress = 36;
/**
* The index of the display for the work postal address.
*/
public static final int dispidWorkAddress = 37;
/**
* The indexes in {@link #MAPI_MAILUSER_PROP_IDS} of the property IDs which
* are to be represented in <tt>SourceContact</tt> as
* <tt>ContactDetail</tt>s.
*/
public static final int[] CONTACT_DETAIL_PROP_INDEXES
= new int[]
{
PR_EMAIL_ADDRESS,
PR_GIVEN_NAME,
PR_MIDDLE_NAME,
PR_SURNAME,
PR_BUSINESS_TELEPHONE_NUMBER,
PR_BUSINESS2_TELEPHONE_NUMBER,
PR_HOME_TELEPHONE_NUMBER,
PR_HOME2_TELEPHONE_NUMBER,
PR_MOBILE_TELEPHONE_NUMBER,
PR_OTHER_TELEPHONE_NUMBER,
dispidEmail1EmailAddress,
dispidEmail2EmailAddress,
dispidEmail3EmailAddress,
PR_COMPANY_NAME,
dispidFax1EmailAddress,
dispidFax2EmailAddress,
dispidFax3EmailAddress,
PR_NICKNAME,
PR_DISPLAY_NAME_PREFIX,
PR_PERSONAL_HOME_PAGE,
PR_BUSINESS_HOME_PAGE,
PR_TITLE,
dispidInstMsg,
PR_BUSINESS_ADDRESS_CITY,
PR_BUSINESS_ADDRESS_COUNTRY,
PR_BUSINESS_ADDRESS_POSTAL_CODE,
PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE,
PR_BUSINESS_ADDRESS_STREET,
PR_HOME_ADDRESS_CITY,
PR_HOME_ADDRESS_COUNTRY,
PR_HOME_ADDRESS_POSTAL_CODE,
PR_HOME_ADDRESS_STATE_OR_PROVINCE,
PR_HOME_ADDRESS_STREET,
dispidHomeAddress,
dispidWorkAddress
};
/**
* The indexes in {@link #MAPI_MAILUSER_PROP_IDS} of the property IDs which
* represent an identifier which can be used for telephony or persistent
* presence.
*/
private static final int[] CONTACT_OPERATION_SET_ABLE_PROP_INDEXES
= new int[]
{
PR_EMAIL_ADDRESS,
PR_BUSINESS_TELEPHONE_NUMBER,
PR_BUSINESS2_TELEPHONE_NUMBER,
PR_HOME_TELEPHONE_NUMBER,
PR_HOME2_TELEPHONE_NUMBER,
PR_MOBILE_TELEPHONE_NUMBER,
PR_OTHER_TELEPHONE_NUMBER,
dispidEmail1EmailAddress,
dispidEmail2EmailAddress,
dispidEmail3EmailAddress,
dispidFax1EmailAddress,
dispidFax2EmailAddress,
dispidFax3EmailAddress,
dispidInstMsg
};
static
{
System.loadLibrary("jmsoutlookaddrbook");
}
/**
* The number of <tt>SourceContact</tt>s matching this <tt>ContactQuery</tt>
* which have been retrieved from Contacts folders. Since each one of them
* may appear multiple times in the Address Book as well, no matching in the
* Address Book will be performed if there is at least one matching
* <tt>SourceContact</tt> in a Contacts folder.
*/
private int mapiMessageCount;
/**
* Boolea used to defined if we already get and logged a read contact
* property error.
*/
private boolean firstIMAPIPropGetPropFailureLogged = false;
/**
* Initializes a new <tt>MsOutlookAddrBookContactQuery</tt> instance to
* be performed by a specific
* <tt>MsOutlookAddrBookContactSourceService</tt>.
*
* @param msoabcss the <tt>MsOutlookAddrBookContactSourceService</tt>
* which is to perform the new <tt>ContactQuery</tt>
* @param query the <tt>Pattern</tt> for which <tt>msoabcss</tt> is being
* queried
*/
public MsOutlookAddrBookContactQuery(
MsOutlookAddrBookContactSourceService msoabcss,
Pattern query)
{
super(msoabcss, query);
if(logger.isTraceEnabled())
logger.trace("Creating new query:" + query);
}
/**
* Calls back to a specific <tt>PtrCallback</tt> for each
* <tt>MAPI_MAILUSER</tt> found in the Address Book of Microsoft Outlook
* which matches a specific <tt>String</tt> query.
*
* @param query the <tt>String</tt> for which the Address Book of Microsoft
* Outlook is to be queried. <b>Warning</b>: Ignored at the time of this
* writing.
* @param callback the <tt>PtrOutlookContactCallback</tt> to be notified
* about the matching <tt>MAPI_MAILUSER</tt>s
*/
public static native void foreachMailUser(
String query,
PtrOutlookContactCallback callback);
public static ContactDetail.Category getCategory(int propIndex)
{
switch (propIndex)
{
case PR_GIVEN_NAME:
case PR_MIDDLE_NAME:
case PR_SURNAME:
case PR_NICKNAME:
case PR_DISPLAY_NAME_PREFIX:
case PR_PERSONAL_HOME_PAGE:
return ContactDetail.Category.Personal;
case PR_COMPANY_NAME:
case PR_BUSINESS_HOME_PAGE:
case PR_TITLE:
return ContactDetail.Category.Organization;
case dispidEmail1EmailAddress:
case dispidEmail2EmailAddress:
case dispidEmail3EmailAddress:
case PR_EMAIL_ADDRESS:
return ContactDetail.Category.Email;
case PR_BUSINESS2_TELEPHONE_NUMBER:
case PR_BUSINESS_TELEPHONE_NUMBER:
case PR_HOME2_TELEPHONE_NUMBER:
case PR_HOME_TELEPHONE_NUMBER:
case PR_MOBILE_TELEPHONE_NUMBER:
case PR_OTHER_TELEPHONE_NUMBER:
case dispidFax1EmailAddress:
case dispidFax2EmailAddress:
case dispidFax3EmailAddress:
return ContactDetail.Category.Phone;
case dispidInstMsg:
return ContactDetail.Category.InstantMessaging;
case PR_BUSINESS_ADDRESS_CITY:
case PR_BUSINESS_ADDRESS_COUNTRY:
case PR_BUSINESS_ADDRESS_POSTAL_CODE:
case PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE:
case PR_BUSINESS_ADDRESS_STREET:
case PR_HOME_ADDRESS_CITY:
case PR_HOME_ADDRESS_COUNTRY:
case PR_HOME_ADDRESS_POSTAL_CODE:
case PR_HOME_ADDRESS_STATE_OR_PROVINCE:
case PR_HOME_ADDRESS_STREET:
case dispidHomeAddress:
case dispidWorkAddress:
return ContactDetail.Category.Address;
default:
return null;
}
}
/**
* Gets the set of <tt>ContactDetail</tt> labels to be assigned to a
* property specified by its index in {@link #MAPI_MAILUSER_PROP_IDS}.
*
* @param propIndex the index in <tt>MAPI_MAILUSER_PROP_IDS</tt> of the
* property to get the <tt>ContactDetail</tt> labels of
* @return the set of <tt>ContactDetail</tt> labels to be assigned to the
* property specified by its index in <tt>MAPI_MAILUSER_PROP_IDS</tt>
*/
public static ContactDetail.SubCategory[] getSubCategories(int propIndex)
{
switch (propIndex)
{
case PR_GIVEN_NAME:
case PR_MIDDLE_NAME:
case PR_COMPANY_NAME:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Name
};
case PR_SURNAME:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.LastName
};
case PR_NICKNAME:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Nickname
};
case PR_BUSINESS2_TELEPHONE_NUMBER:
case PR_BUSINESS_TELEPHONE_NUMBER:
case dispidEmail2EmailAddress:
case PR_EMAIL_ADDRESS:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Work
};
case PR_HOME2_TELEPHONE_NUMBER:
case PR_HOME_TELEPHONE_NUMBER:
case dispidEmail1EmailAddress:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Home
};
case PR_MOBILE_TELEPHONE_NUMBER:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Mobile
};
case PR_OTHER_TELEPHONE_NUMBER:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Other
};
case dispidFax1EmailAddress:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Fax,
};
case dispidEmail3EmailAddress:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Other
};
case PR_TITLE:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.JobTitle
};
case PR_BUSINESS_ADDRESS_CITY:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Work,
ContactDetail.SubCategory.City
};
case PR_BUSINESS_ADDRESS_COUNTRY:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Work,
ContactDetail.SubCategory.Country
};
case PR_BUSINESS_ADDRESS_POSTAL_CODE:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Work,
ContactDetail.SubCategory.PostalCode
};
case PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Work,
ContactDetail.SubCategory.State
};
case PR_BUSINESS_ADDRESS_STREET:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Work,
ContactDetail.SubCategory.Street
};
case PR_HOME_ADDRESS_CITY:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Home,
ContactDetail.SubCategory.City
};
case PR_HOME_ADDRESS_COUNTRY:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Home,
ContactDetail.SubCategory.Country
};
case PR_HOME_ADDRESS_POSTAL_CODE:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Home,
ContactDetail.SubCategory.PostalCode
};
case PR_HOME_ADDRESS_STATE_OR_PROVINCE:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Home,
ContactDetail.SubCategory.State
};
case PR_HOME_ADDRESS_STREET:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Home,
ContactDetail.SubCategory.Street
};
case dispidHomeAddress:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Home,
};
case dispidWorkAddress:
return
new ContactDetail.SubCategory[]
{
ContactDetail.SubCategory.Work,
};
default:
return null;
}
}
/**
* Find the outlook property tag from category and subcategories.
*
* @param category The category.
* @param subCategories The subcategories.
*
* @return The outlook property tag corresponding to the given category and
* subcategories.
*/
public static long getProperty(
ContactDetail.Category category,
Collection<ContactDetail.SubCategory> subCategories)
{
int i = -1;
switch(category)
{
case Personal:
if(subCategories.contains(ContactDetail.SubCategory.Name))
i = PR_GIVEN_NAME;
else if(subCategories.contains(
ContactDetail.SubCategory.LastName))
i = PR_SURNAME;
else if(subCategories.contains(
ContactDetail.SubCategory.Nickname))
i = PR_NICKNAME;
else if(subCategories.contains(
ContactDetail.SubCategory.HomePage))
i = PR_PERSONAL_HOME_PAGE;
else
i = PR_DISPLAY_NAME_PREFIX;
break;
case Organization:
if(subCategories.contains(ContactDetail.SubCategory.Name))
i = PR_COMPANY_NAME;
else if(subCategories.contains(ContactDetail.SubCategory.JobTitle))
i = PR_TITLE;
else
i = PR_BUSINESS_HOME_PAGE;
break;
case Email:
if(subCategories.contains(ContactDetail.SubCategory.Work))
i = dispidEmail2EmailAddress;
else if(subCategories.contains(
ContactDetail.SubCategory.Home))
i = dispidEmail1EmailAddress;
else if(subCategories.contains(
ContactDetail.SubCategory.Other))
i = dispidEmail3EmailAddress;
break;
case Phone:
if(subCategories.contains(ContactDetail.SubCategory.Fax))
i = dispidFax1EmailAddress;
else if(subCategories.contains(ContactDetail.SubCategory.Work))
i = PR_BUSINESS_TELEPHONE_NUMBER;
else if(subCategories.contains(ContactDetail.SubCategory.Home))
i = PR_HOME_TELEPHONE_NUMBER;
else if(subCategories.contains(
ContactDetail.SubCategory.Mobile))
i = PR_MOBILE_TELEPHONE_NUMBER;
else if(subCategories.contains(
ContactDetail.SubCategory.Other))
i = PR_OTHER_TELEPHONE_NUMBER;
break;
case InstantMessaging:
i = dispidInstMsg;
break;
case Address:
if(subCategories.contains(ContactDetail.SubCategory.Work))
{
if(subCategories.contains(ContactDetail.SubCategory.City))
i = PR_BUSINESS_ADDRESS_CITY;
else if(subCategories.contains(
ContactDetail.SubCategory.Country))
i = PR_BUSINESS_ADDRESS_COUNTRY;
else if(subCategories.contains(
ContactDetail.SubCategory.PostalCode))
i = PR_BUSINESS_ADDRESS_POSTAL_CODE;
else if(subCategories.contains(ContactDetail.SubCategory.State))
i = PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE;
else if(subCategories.contains(
ContactDetail.SubCategory.Street))
i = PR_BUSINESS_ADDRESS_STREET;
else
i = dispidWorkAddress;
}
else if(subCategories.contains(ContactDetail.SubCategory.Home))
{
if(subCategories.contains(ContactDetail.SubCategory.City))
i = PR_HOME_ADDRESS_CITY;
else if(subCategories.contains(
ContactDetail.SubCategory.Country))
i = PR_HOME_ADDRESS_COUNTRY;
else if(subCategories.contains(
ContactDetail.SubCategory.PostalCode))
i = PR_HOME_ADDRESS_POSTAL_CODE;
else if(subCategories.contains(ContactDetail.SubCategory.State))
i = PR_HOME_ADDRESS_STATE_OR_PROVINCE;
else if(subCategories.contains(
ContactDetail.SubCategory.Street))
i = PR_HOME_ADDRESS_STREET;
else
i = dispidHomeAddress;
}
break;
case Web:
default:
break;
}
return (i >= 0) ? MAPI_MAILUSER_PROP_IDS[i] : -1;
}
public static native Object[] IMAPIProp_GetProps(
String entryId,
long[] propIds, long flags)
throws MsOutlookMAPIHResultException;
public static native boolean IMAPIProp_SetPropString(
long propId,
String value,
String entryId);
public static native boolean IMAPIProp_DeleteProp(
long propId,
String entryId);
/**
* Removes a contact from the address book.
*
* @param id the person id.
*
* @return whether the contact was successfully removed.
*/
public static native boolean deleteContact(String id);
/**
* Creates an empty contact from the address book.
*
* @return The id of the new contact created. Or NULL if the ceration
* failed.
*/
public static native String createContact();
/**
* Compares two identifiers to determine if they are part of the same
* Outlook contact.
*
* @param id1 The first identifier.
* @param id2 The second identifier.
*
* @return True if id1 and id2 are two identifiers of the same contact.
* False otherwise.
*/
public static native boolean compareEntryIds(String id1, String id2);
/**
* Determines whether a specific index in {@link #MAPI_MAILUSER_PROP_IDS}
* stands for a property with a phone number value.
*
* @param propIndex the index in <tt>MAPI_MAILUSER_PROP_IDS</tt> of the
* property to check
* @return <tt>true</tt> if <tt>propIndex</tt> stands for a property with a
* phone number value; otherwise, <tt>false</tt>
*/
private static boolean isPhoneNumber(int propIndex)
{
switch (propIndex)
{
case PR_BUSINESS2_TELEPHONE_NUMBER:
case PR_BUSINESS_TELEPHONE_NUMBER:
case PR_HOME2_TELEPHONE_NUMBER:
case PR_HOME_TELEPHONE_NUMBER:
case PR_MOBILE_TELEPHONE_NUMBER:
return true;
default:
return false;
}
}
/**
* Determines whether a specific <tt>MAPI_MAILUSER</tt> property with a
* specific <tt>value</tt> matches the {@link #query} of this
* <tt>AsyncContactQuery</tt>.
*
* @param property the <tt>MAPI_MAILUSER</tt> property to check
* @param value the value of the <tt>property</tt> to check
* @return <tt>true</tt> if the specified <tt>value</tt> of the specified
* <tt>property</tt> matches the <tt>query</tt> of this
* <tt>AsyncContactQuery</tt>; otherwise, <tt>false</tt>
*/
private boolean matches(int property, String value)
{
return
query.matcher(value).find()
|| (isPhoneNumber(property) && phoneNumberMatches(value));
}
/**
* Notifies this <tt>MsOutlookAddrBookContactQuery</tt> about a specific
* <tt>MAPI_MAILUSER</tt>.
*
* @param id The outlook contact identifier.
*
* @return <tt>true</tt> if this <tt>MsOutlookAddrBookContactQuery</tt>
* is to continue being called; otherwise, <tt>false</tt>
* @throws MsOutlookMAPIHResultException if anything goes wrong while
* getting the properties of the specified <tt>MAPI_MAILUSER</tt>
*/
private synchronized boolean onMailUser(String id)
throws MsOutlookMAPIHResultException
{
Object[] props = null;
try
{
props
= IMAPIProp_GetProps(id, MAPI_MAILUSER_PROP_IDS, MAPI_UNICODE);
}
catch(MsOutlookMAPIHResultException ex)
{
String hresult = ex.getHresultString();
if (logger.isTraceEnabled())
{
logger.trace(
MsOutlookAddrBookContactQuery.class.getSimpleName()
+ "#onMailUser(String)",
ex);
}
if("MAPI_E_0x57".equals(hresult))
{
if (!firstIMAPIPropGetPropFailureLogged)
{
firstIMAPIPropGetPropFailureLogged = true;
throw ex;
}
}
else
{
throw ex;
}
return true;
}
long objType = 0;
if(props != null
&& props[PR_OBJECT_TYPE] != null
&& props[PR_OBJECT_TYPE] instanceof Long)
{
objType = ((Long) props[PR_OBJECT_TYPE]).longValue();
}
else
{
logger.error("Wrong object types. We are canceling the query.");
return false;
}
// If we have results from the Contacts folder(s), don't read from the
// Address Book because there may be duplicates.
if ((MAPI_MAILUSER == objType) && (mapiMessageCount != 0))
{
if (logger.isTraceEnabled())
{
logger.trace("Duplicate contacts. We are canceling the query.");
}
return false;
}
int propIndex = 0;
boolean matches = false;
Object prop;
for(int i = 0; i < props.length; ++i)
{
prop = props[i];
if ((prop instanceof String)
&& matches(propIndex, (String) prop)
&& i != PR_ORIGINAL_ENTRYID)
{
matches = true;
break;
}
propIndex++;
}
if (matches)
{
List<ContactDetail> contactDetails = getContactDetails(props);
// What's the point of showing a contact who has no contact details?
if (!contactDetails.isEmpty())
{
String displayName = getDisplayName(props);
MsOutlookAddrBookSourceContact sourceContact
= new MsOutlookAddrBookSourceContact(
getContactSource(),
(String) props[PR_ORIGINAL_ENTRYID],
displayName,
getOrganization(props),
contactDetails);
if (MAPI_MESSAGE == objType)
{
++mapiMessageCount;
try
{
Object[] images
= IMAPIProp_GetProps(
id,
new long[] { PR_ATTACHMENT_CONTACTPHOTO },
0);
Object image = images[0];
if (image instanceof byte[])
sourceContact.setImage((byte[]) image);
}
catch (MsOutlookMAPIHResultException ex)
{
// Ignore it, the image isn't as vital as the
// SourceContact.
if (logger.isTraceEnabled())
{
logger.trace(
"Retrieving the image property of the "
+ "contact failed.",
ex);
}
}
}
if(logger.isTraceEnabled())
{
logger.trace(
"For query: " + query + " found contact:"
+ sourceContact.getDisplayName() + ", "
+ sourceContact.getContactAddress());
}
addQueryResult(sourceContact);
}
else
{
logger.error("Error creating the found contact. " +
"Contact details are empty.");
}
}
return (getStatus() == QUERY_IN_PROGRESS);
}
/**
* Gets the <tt>contactDetails</tt> to be set on a <tt>SourceContact</tt>
* which is to represent an <tt>ABPerson</tt>.
*
* @param values the values of the <tt>ABPERSON_PROPERTIES</tt> which
* represent the <tt>ABPerson</tt> to get the <tt>contactDetails</tt> of
* @return the <tt>contactDetails</tt> to be set on a <tt>SourceContact</tt>
* which is to represent the <tt>ABPerson</tt> specified by <tt>values</tt>
*/
public static List<ContactDetail> getContactDetails(Object[] values)
{
List<Class<? extends OperationSet>> supportedOpSets
= new ArrayList<Class<? extends OperationSet>>(2);
supportedOpSets.add(OperationSetBasicTelephony.class);
// can be added as contacts
supportedOpSets.add(OperationSetPersistentPresence.class);
List<ContactDetail> contactDetails = new LinkedList<ContactDetail>();
for (int i = 0; i < CONTACT_DETAIL_PROP_INDEXES.length; i++)
{
int property = CONTACT_DETAIL_PROP_INDEXES[i];
Object value = values[property];
if (value instanceof String)
{
String stringValue = (String) value;
if (stringValue.length() != 0)
{
if(isPhoneNumber(property))
stringValue
= AddrBookActivator.getPhoneNumberI18nService()
.normalize(stringValue);
MsOutlookAddrBookContactDetail contactDetail
= new MsOutlookAddrBookContactDetail(
stringValue,
getCategory(property),
getSubCategories(property),
MAPI_MAILUSER_PROP_IDS[property]);
// Check if this contact detail support the telephony and
// the persistent presence operation set.
for(int j = 0;
j < CONTACT_OPERATION_SET_ABLE_PROP_INDEXES.length;
++j)
{
if(property
== CONTACT_OPERATION_SET_ABLE_PROP_INDEXES[j])
{
contactDetail.setSupportedOpSets(supportedOpSets);
// Found, then break the loop.
j = CONTACT_OPERATION_SET_ABLE_PROP_INDEXES.length;
}
}
contactDetails.add(contactDetail);
}
}
}
return contactDetails;
}
/**
* Performs this <tt>AsyncContactQuery</tt> in a background <tt>Thread</tt>.
*
* @see AsyncContactQuery#run()
*/
@Override
protected void run()
{
synchronized (MsOutlookAddrBookContactQuery.class)
{
long start = System.currentTimeMillis();
foreachMailUser(
query.toString(),
new PtrOutlookContactCallback());
if(logger.isTraceEnabled())
{
logger.trace(
"Query " + query + " took "
+ (System.currentTimeMillis() - start) + " ms.");
}
}
}
/**
* Callback method when receiving notifications for inserted items.
*
* @param id The outlook contact identifier.
*/
public void inserted(String id)
{
synchronized (MsOutlookAddrBookContactQuery.class)
{
insertedOrUpdated(id, 0);
}
}
/**
* Callback method when receiving notifications for updated items.
*
* @param id The outlook contact identifier.
*/
public void updated(String id)
{
synchronized (MsOutlookAddrBookContactQuery.class)
{
insertedOrUpdated(id, 1);
}
}
/**
* Callback method when receiving notifications for updated items.
*
* @param id The outlook contact identifier.
* @param maxLevel The maximum level for comparing ids: 0 cached ids only, 1
* cached ids and outlook database ids.
*/
public void insertedOrUpdated(String id, int maxLevel)
{
SourceContact sourceContact = findSourceContactByID(id, maxLevel);
if(sourceContact != null
&& sourceContact instanceof MsOutlookAddrBookSourceContact)
{
// updated
((MsOutlookAddrBookSourceContact) sourceContact).updated();
fireContactChanged(sourceContact);
}
else
{
// inserted
try
{
onMailUser(id);
}
catch (MsOutlookMAPIHResultException e)
{
if (logger.isDebugEnabled())
{
logger.debug(
MsOutlookAddrBookContactQuery.class.getSimpleName()
+ "#onMailUser(String)",
e);
}
}
}
}
/**
* Callback method when receiving notifications for deleted items.
*
* @param id The outlook contact identifier.
*/
public void deleted(String id)
{
if(id != null)
{
synchronized (MsOutlookAddrBookContactQuery.class)
{
SourceContact sourceContact = findSourceContactByID(id, 1);
if(sourceContact != null)
{
fireContactRemoved(sourceContact);
}
}
}
}
/**
* Callback to called by the native outlook part with a contact id as
* argument.
*/
public class PtrOutlookContactCallback
{
/**
* Notifies this callback about a specific contact.
*
* @param id The outlook contact identifier.
*
* @return <tt>true</tt> if this <tt>PtrCallback</tt> is to continue
* being called; otherwise, <tt>false</tt>
*/
boolean callback(String id)
{
try
{
return onMailUser(id);
}
catch (MsOutlookMAPIHResultException e)
{
if (logger.isDebugEnabled())
{
logger.debug(
MsOutlookAddrBookContactQuery.class.getSimpleName()
+ "#onMailUser(String)",
e);
}
return false;
}
}
}
/**
* Adds a new empty contact, which will be filled in later.
*
* @param id The ID of the contact to add.
*/
public void addEmptyContact(String id)
{
if(id != null)
{
final MsOutlookAddrBookSourceContact sourceContact
= new MsOutlookAddrBookSourceContact(
getContactSource(),
id,
null,
null,
new LinkedList<ContactDetail>());
addQueryResult(sourceContact);
}
}
/**
* Gets the <tt>displayName</tt> to be set on a <tt>SourceContact</tt>.
*
* @param values the values of the contact properties.
*
* @return the <tt>displayName</tt> to be set on a <tt>SourceContact</tt>.
*/
public static String getDisplayName(Object[] values)
{
String displayName = (String) values[PR_NICKNAME];
if ((displayName == null) || (displayName.length() == 0))
{
String firstName = (String) values[PR_GIVEN_NAME];
String lastName = (String) values[PR_SURNAME];
if ((lastName == null) || (lastName.length() == 0))
lastName = (String) values[PR_MIDDLE_NAME];
if ((firstName == null) || (firstName.length() == 0))
displayName = lastName;
else
{
displayName = firstName;
if ((lastName != null) && (lastName.length() != 0))
displayName += " " + lastName;
}
}
if ((displayName == null) || (displayName.length() == 0))
displayName = (String) values[PR_COMPANY_NAME];
if ((displayName == null) || (displayName.length() == 0))
{
for(int i = 0; i < values.length; ++i)
{
if(values[i] instanceof String)
{
displayName = (String) values[i];
if ((displayName != null) && (displayName.length() != 0))
return displayName;
}
}
}
return displayName;
}
/**
* Gets the organization name to be set on a <tt>SourceContact</tt>.
*
* @param values the values of the contact properties.
*
* @return the organization name to be set on a <tt>SourceContact</tt>.
*/
public static String getOrganization(Object[] values)
{
return (String) values[PR_COMPANY_NAME];
}
/**
* Searches for source contact with the specified id.
*
* @param id the id to search for.
* @param maxLevel The maximum level for comparing ids: 0 cached ids only, 1
* cached ids and outlook database ids.
*
* @return the source contact found or null.
*/
protected SourceContact findSourceContactByID(String id, int maxLevel)
{
synchronized(sourceContacts)
{
for(int level = 0; level <= maxLevel; ++level)
{
for(SourceContact sc : sourceContacts)
{
if(sc instanceof MsOutlookAddrBookSourceContact
&& ((MsOutlookAddrBookSourceContact) sc)
.match(id, level))
{
return sc;
}
}
}
}
// not found
return null;
}
}