/*
* 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 net.java.sip.communicator.service.contactsource.*;
import net.java.sip.communicator.util.*;
/**
* Implements a custom <tt>SourceContact</tt> for the Address Book of Microsoft
* Outlook.
*
* @author Vincent Lucas
*/
public class MsOutlookAddrBookSourceContact
extends GenericSourceContact
implements EditableSourceContact
{
/**
* The <tt>Logger</tt> used by the <tt>MsOutlookAddrBookSourceContact</tt>
* class and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(MsOutlookAddrBookSourceContact.class);
/**
* Boolean used to temporarily lock the access to a single modification
* source (jitsi or contact). i.e. it can be useful if Jitsi modifies a
* batch of details and do not want to receive contact update notification
* which can produce concurrent changes of the details.
*/
private Boolean locked = Boolean.FALSE;
/**
* The list of Outlook entry IDs we have already seen for this contact.
*/
private Vector<String> ids = new Vector<String>(1, 1);
/**
* Initializes a new MsOutlookAddrBookSourceContact instance.
*
* @param contactSource The ContactSourceService which is creating the new
* instance.
* @param id The outlook entry identifier for contacts.
* @param displayName The display name of the new instance.
* @param organization The organization name of the new instance.
* @param contactDetails The ContactDetails of the new instance.
*/
public MsOutlookAddrBookSourceContact(
ContactSourceService contactSource,
String id,
String displayName,
String organization,
List<ContactDetail> contactDetails)
{
super(contactSource, displayName, contactDetails);
this.setData(SourceContact.DATA_ID, id);
this.setDisplayDetails(organization);
this.ids.add(id);
}
/**
* Returns the string identifier for this source contact.
*
* @return The string identifier for this source contact.
*/
public String getId()
{
return (String) this.getData(SourceContact.DATA_ID);
}
/**
* Changes the details list with the supplied one. Called when the outlook
* database for this source contact has been updated.
*
* @param details the details.
*/
public void setDetails(List<ContactDetail> details)
{
synchronized(this)
{
contactDetails.clear();
contactDetails.addAll(details);
}
}
/**
* Saves all the properties from this source contact into the outlook
* database.
*/
public void save()
{
synchronized(this)
{
setDisplayPostalAddress();
MsOutlookAddrBookContactDetail outlookContactDetail;
for(ContactDetail contactDetail: this.contactDetails)
{
if(contactDetail instanceof MsOutlookAddrBookContactDetail)
{
outlookContactDetail
= (MsOutlookAddrBookContactDetail) contactDetail;
for(Long propId: outlookContactDetail.getOutlookPropId())
{
MsOutlookAddrBookContactQuery.IMAPIProp_SetPropString(
propId.longValue(),
contactDetail.getDetail(),
this.getId());
}
}
}
}
}
/**
* Removes the given <tt>ContactDetail</tt> from the list of details for
* this <tt>SourceContact</tt>.
*
* @param detail the <tt>ContactDetail</tt> to remove
*/
public void removeContactDetail(ContactDetail detail)
{
synchronized(this)
{
int i = 0;
while(i < this.contactDetails.size())
{
MsOutlookAddrBookContactDetail contactDetail
= ((MsOutlookAddrBookContactDetail)
this.contactDetails.get(i));
if(contactDetail.match(detail))
{
this.removeProperty(contactDetail);
this.contactDetails.remove(i);
}
else
{
++i;
}
}
}
}
/**
* Removes the contact detail from the outlook database.
*
* @param contactDetail The contact detail to remove.
*/
public void removeProperty(
final MsOutlookAddrBookContactDetail contactDetail)
{
for(Long propId: contactDetail.getOutlookPropId())
{
MsOutlookAddrBookContactQuery.IMAPIProp_DeleteProp(
propId.longValue(),
this.getId());
}
}
/**
* Adds a contact detail to the list of contact details.
*
* @param detail the <tt>ContactDetail</tt> to add
*/
public void addContactDetail(ContactDetail detail)
{
synchronized(this)
{
MsOutlookAddrBookContactDetail addDetail;
if(!(detail instanceof MsOutlookAddrBookContactDetail))
{
long property = MsOutlookAddrBookContactQuery.getProperty(
detail.getCategory(),
detail.getSubCategories());
Collection<ContactDetail.SubCategory> subCategories
= detail.getSubCategories();
addDetail = new MsOutlookAddrBookContactDetail(
detail.getDetail(),
detail.getCategory(),
subCategories.toArray(
new ContactDetail.SubCategory[
subCategories.size()]),
property);
}
else
{
addDetail = (MsOutlookAddrBookContactDetail) detail;
}
// Checks if this property already exists.
for(int i = 0; i < this.contactDetails.size(); ++ i)
{
MsOutlookAddrBookContactDetail contactDetail
= ((MsOutlookAddrBookContactDetail)
this.contactDetails.get(i));
if(contactDetail.match(addDetail))
{
return;
}
}
this.contactDetails.add(addDetail);
}
}
/**
* Sets the display name for this contact.
*
* @param displayName The new display name for this contact.
*/
@Override
public void setDisplayName(String displayName)
{
if(displayName != null && !displayName.equals(this.getDisplayName()))
{
// Be sure that the new determined display named is saved under all
// the requireed properties.
long[] displayNamePropIdList =
{
0x3001, // PR_DISPLAY_NAME
0x0037, // PR_SUBJECT
0x803F, // Do not know, but set by the MFCMAPI application.
0x0E1D // PR_NORMALIZED_SUBJECT
};
for(int i = 0; i < displayNamePropIdList.length; ++i)
{
MsOutlookAddrBookContactQuery.IMAPIProp_SetPropString(
displayNamePropIdList[i],
displayName,
this.getId());
}
}
super.setDisplayName(displayName);
}
/**
* Function called by the native part (msoutlook) when this contact has been
* updated.
*/
public void updated()
{
// Synchronize before the GetProps in order to let other operation (i.e.
// save) to write/change all desired values (and not override new saved
// values iwth old ones).
synchronized(this)
{
waitUnlock();
Object[] props = null;
try
{
props = MsOutlookAddrBookContactQuery.IMAPIProp_GetProps(
this.getId(),
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS,
MsOutlookAddrBookContactQuery.MAPI_UNICODE);
}
catch (MsOutlookMAPIHResultException e)
{
if (logger.isDebugEnabled())
{
logger.debug(
MsOutlookAddrBookContactQuery.class.getSimpleName()
+ "#IMAPIProp_GetProps(long, long[], long)",
e);
}
}
// let's update the the details
List<ContactDetail> contactDetails
= MsOutlookAddrBookContactQuery.getContactDetails(props);
this.setDetails(contactDetails);
String displayName
= MsOutlookAddrBookContactQuery.getDisplayName(props);
this.setDisplayName(displayName);
String organization
= MsOutlookAddrBookContactQuery.getOrganization(props);
this.setDisplayDetails(organization);
}
}
/**
* Locks this object before adding or removing several contact details.
*/
public void lock()
{
synchronized(this)
{
locked = Boolean.TRUE;
}
}
/**
* Unlocks this object before after or removing several contact details.
*/
public void unlock()
{
synchronized(this)
{
this.save();
locked = Boolean.FALSE;
notify();
}
}
/**
* Waits to be unlocked. This object must be synchronized before calling
* this function.
*/
private void waitUnlock()
{
boolean continueToWait = this.locked;
while(continueToWait)
{
try
{
wait();
continueToWait = false;
}
catch(InterruptedException ie)
{
// Nothing to do, we will wait until the notify.
}
}
}
/**
* Returns the index of this source contact in its parent.
*
* @return the index of this source contact in its parent
*/
@Override
public int getIndex()
{
return -1;
}
/**
* Tells if the id given in parameters corresponds to this contact.
*
* @param id The id to compare with.
* @param level 0 to only look at cached ids. 1 to only look at outlook
* database ids.
*
* @return True if the id given in parameters corresponds to this contact.
* False otherwise.
*/
public boolean match(String id, int level)
{
boolean res = false;
switch(level)
{
case 0:
res = this.ids.contains(id);
break;
case 1:
String localId = this.getId();
res =
MsOutlookAddrBookContactQuery.compareEntryIds(id, localId);
if(res && !this.ids.contains(id))
{
this.ids.add(id);
}
break;
}
return res;
}
/**
* Generates and stores the string representation of the home and work
* postall addresses.
*/
private void setDisplayPostalAddress()
{
synchronized(this)
{
MsOutlookAddrBookContactDetail detail;
// Setting the display work postal address.
boolean firstLineCR = false;
boolean secondLineCR = false;
String workAddress = "";
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.PR_BUSINESS_ADDRESS_STREET]);
if(detail != null)
{
workAddress += detail.getDetail();
firstLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.PR_BUSINESS_ADDRESS_CITY]);
if(detail != null)
{
if(firstLineCR)
{
workAddress += "\r";
firstLineCR = false;
}
workAddress += detail.getDetail();
workAddress += " ";
secondLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.
PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE]);
if(detail != null)
{
if(firstLineCR)
{
workAddress += "\r";
firstLineCR = false;
}
workAddress += detail.getDetail();
secondLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.
PR_BUSINESS_ADDRESS_POSTAL_CODE]);
if(detail != null)
{
if(firstLineCR)
{
workAddress += "\r";
firstLineCR = false;
}
workAddress += detail.getDetail();
workAddress += " ";
secondLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.PR_BUSINESS_ADDRESS_COUNTRY]);
if(detail != null)
{
if(secondLineCR)
{
workAddress += "\r";
secondLineCR = false;
}
workAddress += detail.getDetail();
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.dispidWorkAddress]);
if(detail != null)
{
// set address.
detail.setDetail(workAddress);
}
else if(workAddress.length() > 0)
{
detail = new MsOutlookAddrBookContactDetail(
workAddress,
MsOutlookAddrBookContactQuery.getCategory(
MsOutlookAddrBookContactQuery.dispidWorkAddress),
MsOutlookAddrBookContactQuery.getSubCategories(
MsOutlookAddrBookContactQuery.dispidWorkAddress),
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.dispidWorkAddress]);
this.contactDetails.add(detail);
}
// Setting the display home postal address.
firstLineCR = false;
secondLineCR = false;
String homeAddress = "";
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.PR_HOME_ADDRESS_STREET]);
if(detail != null)
{
homeAddress += detail.getDetail();
firstLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.PR_HOME_ADDRESS_CITY]);
if(detail != null)
{
if(firstLineCR)
{
homeAddress += "\r";
firstLineCR = false;
}
homeAddress += detail.getDetail();
homeAddress += " ";
secondLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.
PR_HOME_ADDRESS_STATE_OR_PROVINCE]);
if(detail != null)
{
if(firstLineCR)
{
homeAddress += "\r";
firstLineCR = false;
}
homeAddress += detail.getDetail();
secondLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.PR_HOME_ADDRESS_POSTAL_CODE]);
if(detail != null)
{
if(firstLineCR)
{
homeAddress += "\r";
firstLineCR = false;
}
homeAddress += detail.getDetail();
homeAddress += " ";
secondLineCR = true;
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.PR_HOME_ADDRESS_COUNTRY]);
if(detail != null)
{
if(secondLineCR)
{
homeAddress += "\r";
secondLineCR = false;
}
homeAddress += detail.getDetail();
}
detail = findDetail(
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.dispidHomeAddress]);
if(detail != null)
{
// set address.
detail.setDetail(homeAddress);
}
else if(homeAddress.length() > 0)
{
detail = new MsOutlookAddrBookContactDetail(
homeAddress,
MsOutlookAddrBookContactQuery.getCategory(
MsOutlookAddrBookContactQuery.dispidHomeAddress),
MsOutlookAddrBookContactQuery.getSubCategories(
MsOutlookAddrBookContactQuery.dispidHomeAddress),
MsOutlookAddrBookContactQuery.MAPI_MAILUSER_PROP_IDS[
MsOutlookAddrBookContactQuery.dispidHomeAddress]);
this.contactDetails.add(detail);
}
}
}
/**
* Finds the detail corresponding to the given property id.
*
* @param detailPropId The detail identifier.
*
* @return The detail corresponding to the given property id. Null if not
* found.
*/
private MsOutlookAddrBookContactDetail findDetail(long detailPropId)
{
synchronized(this)
{
MsOutlookAddrBookContactDetail outlookContactDetail;
for(ContactDetail contactDetail: this.contactDetails)
{
if(contactDetail instanceof MsOutlookAddrBookContactDetail)
{
outlookContactDetail
= (MsOutlookAddrBookContactDetail) contactDetail;
for(Long propId: outlookContactDetail.getOutlookPropId())
{
if(propId.longValue() == detailPropId)
{
return outlookContactDetail;
}
}
}
}
}
return null;
}
}