/*
* 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.impl.contactlist;
import java.util.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.contactlist.event.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
import org.jitsi.util.xml.*;
import org.osgi.framework.*;
/**
* An implementation of the MetaContactListService that would connect to
* protocol service providers and build it s contact list accordingly
* basing itself on the contact list stored by the various protocol provider
* services and the contact list instance saved on the hard disk.
* <p>
*
* @author Emil Ivov
*/
public class MetaContactListServiceImpl
implements MetaContactListService,
ServiceListener,
ContactPresenceStatusListener,
ContactCapabilitiesListener
{
/**
* The <tt>Logger</tt> used by the <tt>MetaContactListServiceImpl</tt> class
* and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(MetaContactListServiceImpl.class);
/**
* The BundleContext that we got from the OSGI bus.
*/
private BundleContext bundleContext = null;
/**
* The list of protocol providers that we're currently aware of.
*/
private final Map<String, ProtocolProviderService> currentlyInstalledProviders
= new Hashtable<String, ProtocolProviderService>();
/**
* The root of the meta contact list.
*/
final MetaContactGroupImpl rootMetaGroup;
/**
* The event handler that will be handling our subscription events.
*/
private final ContactListSubscriptionListener clSubscriptionEventHandler
= new ContactListSubscriptionListener();
/**
* The event handler that will be handling group events.
*/
private final ContactListGroupListener clGroupEventHandler
= new ContactListGroupListener();
/**
* The number of milliseconds to wait for confirmations of account
* modifications before deciding to drop.
*/
public static final int CONTACT_LIST_MODIFICATION_TIMEOUT = 10000;
/**
* Listeners interested in events dispatched upon modification of the meta
* contact list.
*/
private final List<MetaContactListListener> metaContactListListeners
= new Vector<MetaContactListListener>();
/**
* Contains (as keys) <tt>MetaContactGroup</tt> names that are currently
* being resolved against a given protocol and that this class's
* <tt>ContactGroupListener</tt> should ignore as corresponding events will
* be handled by the corresponding methods. The table maps the meta contact
* group names against lists of protocol providers. An incoming group event
* would therefore be ignored by the class group listener if and only if it
* carries a name present in this table and is issued by one of the
* providers mapped against this groupName.
*/
private final Hashtable<String, List<ProtocolProviderService>>
groupEventIgnoreList = new Hashtable<String,
List<ProtocolProviderService>>();
/**
* Contains (as keys) <tt>Contact</tt> addresses that are currently
* being resolved against a given protocol and that this class's
* <tt>ContactListener</tt> should ignore as corresponding events will
* be handled by the corresponding methods. The table maps the meta contact
* addresses against lists of protocol providers. An incoming group event
* would therefore be ignored by the class group listener if and only if it
* carries a name present in this table and is issued by one of the
* providers mapped against this groupName.
*/
private final Hashtable<String, List<ProtocolProviderService>>
contactEventIgnoreList = new Hashtable<String,
List<ProtocolProviderService>>();
/**
* The instance of the storage manager which is handling the local copy of
* our contact list.
*/
private final MclStorageManager storageManager = new MclStorageManager();
/**
* Creates an instance of this class.
*/
public MetaContactListServiceImpl()
{
rootMetaGroup
= new MetaContactGroupImpl(
this, ContactlistActivator.getResources().getI18NString(
"service.gui.CONTACTS"), "RootMetaContactGroup");
}
/**
* Starts this implementation of the MetaContactListService. The
* implementation would first restore a default contact list from what has
* been stored in a file. It would then connect to OSGI and retrieve any
* existing protocol providers and if <br>
* 1) They provide implementations of OperationSetPersistentPresence, it
* would synchronize their contact lists with the local one (adding
* subscriptions for contacts that do not exist in the server stored contact
* list and adding locally contacts that were found on the server but not in
* the local file).
* <p>
* 2) The only provide non persistent implementations of
* OperationSetPresence, the meta contact list impl would create
* subscriptions for all local contacts in the corresponding protocol
* provider.
* <p>
* This implementation would also start listening for any newly registered
* protocol provider implementations and perform the same algorithm with
* them.
* <p>
*
* @param bc the currently valid OSGI bundle context.
*/
public void start(BundleContext bc)
{
if (logger.isDebugEnabled())
logger.debug("Starting the meta contact list implementation.");
this.bundleContext = bc;
//initialize the meta contact list from what has been stored locally.
try
{
storageManager.start(bundleContext, this);
}
catch (Exception ex)
{
logger.error("Failed loading the stored contact list.", ex);
}
// start listening for newly register or removed protocol providers
bc.addServiceListener(this);
// first discover the icq service
// then find the protocol provider service
Collection<ServiceReference<ProtocolProviderService>> ppsRefs
= ServiceUtils.getServiceReferences(
bc,
ProtocolProviderService.class);
// in case we found any, retrieve the root groups for all protocol
// providers and create the meta contact list
if (!ppsRefs.isEmpty())
{
if (logger.isDebugEnabled())
{
logger.debug(
"Found " + ppsRefs.size()
+ " already installed providers.");
}
for (ServiceReference<ProtocolProviderService> ppsRef : ppsRefs)
{
ProtocolProviderService pps = bc.getService(ppsRef);
handleProviderAdded(pps);
}
}
}
/**
* Prepares the meta contact list service for shutdown.
*
* @param bc the currently active bundle context.
*/
public void stop(BundleContext bc)
{
storageManager.storeContactListAndStopStorageManager();
bc.removeServiceListener(this);
//stop listening to all currently installed providers
for (ProtocolProviderService pp : currentlyInstalledProviders.values())
{
OperationSetPersistentPresence opSetPersPresence =
pp.getOperationSet(OperationSetPersistentPresence.class);
if(opSetPersPresence !=null)
{
opSetPersPresence
.removeContactPresenceStatusListener(this);
opSetPersPresence
.removeSubscriptionListener(clSubscriptionEventHandler);
opSetPersPresence
.removeServerStoredGroupChangeListener(clGroupEventHandler);
}
else
{
//check if a non persistent presence operation set exists.
OperationSetPresence opSetPresence =
pp.getOperationSet(OperationSetPresence.class);
if(opSetPresence != null)
{
opSetPresence
.removeContactPresenceStatusListener(this);
opSetPresence
.removeSubscriptionListener(clSubscriptionEventHandler);
}
}
}
currentlyInstalledProviders.clear();
storageManager.stop();
}
/**
* Adds a listener for <tt>MetaContactListChangeEvent</tt>s posted after
* the tree changes.
*
* @param listener the listener to add
*/
public void addMetaContactListListener(MetaContactListListener listener)
{
synchronized (metaContactListListeners)
{
if(!metaContactListListeners.contains(listener))
metaContactListListeners.add(listener);
}
}
/**
* First makes the specified protocol provider create the contact as
* indicated by <tt>contactID</tt>, and then associates it to the
* _existing_ <tt>metaContact</tt> given as an argument.
*
* @param provider
* the ProtocolProviderService that should create the contact
* indicated by <tt>contactID</tt>.
* @param metaContact
* the meta contact where that the newly created contact should
* be associated to.
* @param contactID
* the identifier of the contact that the specified provider
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
public void addNewContactToMetaContact(
ProtocolProviderService provider,
MetaContact metaContact, String contactID)
throws MetaContactListException
{
addNewContactToMetaContact(provider, metaContact, contactID, true);
}
/**
* First makes the specified protocol provider create the contact as
* indicated by <tt>contactID</tt>, and then associates it to the
* _existing_ <tt>metaContact</tt> given as an argument.
*
* @param provider
* the ProtocolProviderService that should create the contact
* indicated by <tt>contactID</tt>.
* @param metaContact
* the meta contact where that the newly created contact should
* be associated to.
* @param contactID
* the identifier of the contact that the specified provider
* @param fireEvent
* specifies whether or not an even is to be fire at
* the end of the method.Used when this method is called upon creation of a
* new meta contact and not only a new contact.
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
public void addNewContactToMetaContact( ProtocolProviderService provider,
MetaContact metaContact,
String contactID,
boolean fireEvent)
throws MetaContactListException
{
//find the parent group in the corresponding protocol.
MetaContactGroup parentMetaGroup
= findParentMetaContactGroup(metaContact);
if (parentMetaGroup == null)
{
throw new MetaContactListException(
"orphan Contact: " + metaContact
, null
, MetaContactListException.CODE_NETWORK_ERROR);
}
addNewContactToMetaContact(provider, parentMetaGroup, metaContact,
contactID, fireEvent);
}
/**
* First makes the specified protocol provider create the contact as
* indicated by <tt>contactID</tt>, and then associates it to the
* _existing_ <tt>metaContact</tt> given as an argument.
*
* @param provider
* the ProtocolProviderService that should create the contact
* indicated by <tt>contactID</tt>.
* @param parentMetaGroup
* the meta contact group which is the parent group of the newly
* created contact
* @param metaContact
* the meta contact where that the newly created contact should
* be associated to.
* @param contactID
* the identifier of the contact that the specified provider
* @param fireEvent
* specifies whether or not an even is to be fired at
* the end of the method.Used when this method is called upon creation of a
* new meta contact and not only a new contact.
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
private void addNewContactToMetaContact( ProtocolProviderService provider,
MetaContactGroup parentMetaGroup,
MetaContact metaContact,
String contactID,
boolean fireEvent)
throws MetaContactListException
{
OperationSetPersistentPresence opSetPersPresence =
provider.getOperationSet(OperationSetPersistentPresence.class);
if (opSetPersPresence == null)
{
/** @todo handle non-persistent presence operation sets as well */
return;
}
if (! (metaContact instanceof MetaContactImpl))
{
throw new IllegalArgumentException(
metaContact
+ " is not an instance of MetaContactImpl");
}
ContactGroup parentProtoGroup
= resolveProtoPath(provider, (MetaContactGroupImpl) parentMetaGroup);
if (parentProtoGroup == null)
{
throw new MetaContactListException(
"Could not obtain proto group parent for " + metaContact
, null
, MetaContactListException.CODE_NETWORK_ERROR);
}
BlockingSubscriptionEventRetriever evtRetriever
= new BlockingSubscriptionEventRetriever(contactID);
addContactToEventIgnoreList(contactID, provider);
opSetPersPresence.addSubscriptionListener(evtRetriever);
opSetPersPresence.addServerStoredGroupChangeListener(evtRetriever);
try
{
//create the contact in the group
// if its the root group just call subscribe
if(parentMetaGroup.equals(rootMetaGroup))
opSetPersPresence.subscribe(contactID);
else
opSetPersPresence.subscribe(parentProtoGroup, contactID);
//wait for a confirmation event
evtRetriever.waitForEvent(CONTACT_LIST_MODIFICATION_TIMEOUT);
}
catch(OperationFailedException ex)
{
if(ex.getErrorCode()
== OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS)
{
throw new MetaContactListException(
"failed to create contact " + contactID
, ex
, MetaContactListException.CODE_CONTACT_ALREADY_EXISTS_ERROR);
}
else if(ex.getErrorCode()
== OperationFailedException.NOT_SUPPORTED_OPERATION)
{
throw new MetaContactListException(
"failed to create contact " + contactID
, ex
, MetaContactListException.CODE_NOT_SUPPORTED_OPERATION);
}
throw new MetaContactListException(
"failed to create contact " + contactID
, ex
, MetaContactListException.CODE_NETWORK_ERROR);
}
catch (Exception ex)
{
throw new MetaContactListException(
"failed to create contact " + contactID
, ex
, MetaContactListException.CODE_NETWORK_ERROR);
}
finally
{
//whatever happens we need to remove the event collector
//end the ignore filter.
removeContactFromEventIgnoreList(contactID, provider);
opSetPersPresence.removeSubscriptionListener(evtRetriever);
}
//attach the newly created contact to a meta contact
if (evtRetriever.evt == null)
{
throw new MetaContactListException(
"Failed to create a contact with address: "
+ contactID
, null
, MetaContactListException.CODE_NETWORK_ERROR);
}
if (evtRetriever.evt instanceof SubscriptionEvent &&
((SubscriptionEvent)evtRetriever.evt).getEventID() ==
SubscriptionEvent.SUBSCRIPTION_FAILED)
{
throw new MetaContactListException(
"Failed to create a contact with address: "
+ contactID + " "
+ ((SubscriptionEvent)evtRetriever.evt).getErrorReason()
, null
, MetaContactListException.CODE_UNKNOWN_ERROR);
}
//now finally - add the contact to the meta contact
( (MetaContactImpl) metaContact).addProtoContact(
evtRetriever.sourceContact);
//only fire an event here if the calling method wants us to. in case
//this is the creation of a new contact and not only addition of a
//proto contact we should remain silent and the calling method will
//do the eventing.
if(fireEvent)
{
this.fireProtoContactEvent(evtRetriever.sourceContact,
ProtoContactEvent.PROTO_CONTACT_ADDED,
null,
metaContact);
}
((MetaContactGroupImpl) parentMetaGroup).addMetaContact(
(MetaContactImpl)metaContact);
}
/**
* Makes sure that directories in the whole path from the root to the
* specified group have corresponding directories in the protocol indicated
* by <tt>protoProvider</tt>. The method does not return before creating
* all groups has completed.
*
* @param protoProvider a reference to the protocol provider where the
* groups should be created.
* @param metaGroup a ref to the last group of the path that should be
* created in the specified <tt>protoProvider</tt>
*
* @return e reference to the newly created <tt>ContactGroup</tt>
*/
private ContactGroup resolveProtoPath(ProtocolProviderService protoProvider,
MetaContactGroupImpl metaGroup)
{
Iterator<ContactGroup> contactGroupsForProv = metaGroup
.getContactGroupsForProvider(protoProvider);
if (contactGroupsForProv.hasNext())
{
//we already have at least one group corresponding to the meta group
return contactGroupsForProv.next();
}
//we don't have a proto group here. obtain a ref to the parent
//proto group (which may be created along the way) and create it.
MetaContactGroupImpl parentMetaGroup = (MetaContactGroupImpl)
findParentMetaContactGroup(metaGroup);
if (parentMetaGroup == null)
{
if (logger.isDebugEnabled())
logger.debug("Resolve failed at group" + metaGroup);
throw new NullPointerException("Internal Error. Orphan group.");
}
OperationSetPersistentPresence opSetPersPresence =
protoProvider.getOperationSet(OperationSetPersistentPresence.class);
//if persistent presence is not supported - just bail
//we should have verified this earlier anyway
if (opSetPersPresence == null)
{
return null;
}
ContactGroup parentProtoGroup;
// special treatment for the root group (stop the recursion)
if (parentMetaGroup.getParentMetaContactGroup() == null) {
parentProtoGroup = opSetPersPresence.
getServerStoredContactListRoot();
} else {
parentProtoGroup = resolveProtoPath(protoProvider, parentMetaGroup);
}
//create the proto group
BlockingGroupEventRetriever evtRetriever
= new BlockingGroupEventRetriever(metaGroup.getGroupName());
opSetPersPresence.addServerStoredGroupChangeListener(evtRetriever);
addGroupToEventIgnoreList(metaGroup.getGroupName(), protoProvider);
try
{
//create the group
opSetPersPresence.createServerStoredContactGroup(
parentProtoGroup, metaGroup.getGroupName());
//wait for a confirmation event
evtRetriever.waitForEvent(CONTACT_LIST_MODIFICATION_TIMEOUT);
}
catch (Exception ex)
{
throw new MetaContactListException(
"failed to create contact group " + metaGroup.getGroupName()
, ex
, MetaContactListException.CODE_NETWORK_ERROR);
}
finally
{
//whatever happens we need to remove the event collector
//and the ignore filter.
removeGroupFromEventIgnoreList(metaGroup.getGroupName()
, protoProvider);
opSetPersPresence.removeServerStoredGroupChangeListener(
evtRetriever);
}
//sth went wrong.
if (evtRetriever.evt == null)
{
throw new MetaContactListException(
"Failed to create a proto group named: "
+ metaGroup.getGroupName()
, null
, MetaContactListException.CODE_NETWORK_ERROR);
}
//now add the proto group to the meta group.
metaGroup.addProtoGroup(evtRetriever.evt.getSourceGroup());
fireMetaContactGroupEvent(
metaGroup
, evtRetriever.evt.getSourceProvider()
, evtRetriever.evt.getSourceGroup()
, MetaContactGroupEvent.CONTACT_GROUP_ADDED_TO_META_GROUP);
return evtRetriever.evt.getSourceGroup();
}
/**
* Returns the meta contact group that is a direct parent of the specified
* <tt>child</tt>. If no parent is found <tt>null</tt> is returned.
* @param child the <tt>MetaContactGroup</tt> whose parent group we're
* looking for. If no parent is found <tt>null</tt> is returned.
*
* @return the <tt>MetaContactGroup</tt> that contains <tt>child</tt> or
* null if no parent was found.
*/
public MetaContactGroup findParentMetaContactGroup(MetaContactGroup child)
{
return findParentMetaContactGroup(rootMetaGroup, child);
}
/**
* Returns the meta contact group that is a direct parent of the specified
* <tt>child</tt>, beginning the search at the specified root. If
* no parent is found <tt>null</tt> is returned.
* @param child the <tt>MetaContactGroup</tt> whose parent group we're
* looking for.
* @param root the parent where the search should start.
* @return the <tt>MetaContactGroup</tt> that contains <tt>child</tt> or
* null if no parent was found.
*/
private MetaContactGroup findParentMetaContactGroup(
MetaContactGroupImpl root, MetaContactGroup child)
{
return child.getParentMetaContactGroup();
}
/**
* Returns the meta contact group that is a direct parent of the specified
* <tt>child</tt>.
* @param child the <tt>MetaContact</tt> whose parent group we're looking
* for.
*
* @return the <tt>MetaContactGroup</tt>
* @throws IllegalArgumentException if <tt>child</tt> is not an instance of
* MetaContactImpl
*/
public MetaContactGroup findParentMetaContactGroup(MetaContact child)
{
if (! (child instanceof MetaContactImpl))
{
throw new IllegalArgumentException(child
+ " is not a MetaContactImpl instance.");
}
return ( (MetaContactImpl) child).getParentGroup();
}
/**
* First makes the specified protocol provider create a contact
* corresponding to the specified <tt>contactID</tt>, then creates a new
* MetaContact which will encapsulate the newly created protocol specific
* contact.
*
* @param provider
* a ref to <tt>ProtocolProviderService</tt> instance which
* will create the actual protocol specific contact.
* @param metaContactGroup
* the MetaContactGroup where the newly created meta contact
* should be stored.
* @param contactID
* a protocol specific string identifier indicating the contact
* the protocol provider should create.
* @return the newly created <tt>MetaContact</tt>
*
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
public MetaContact createMetaContact( ProtocolProviderService provider,
MetaContactGroup metaContactGroup,
String contactID)
throws MetaContactListException
{
if (! (metaContactGroup instanceof MetaContactGroupImpl))
{
throw new IllegalArgumentException(metaContactGroup
+ " is not an instance of MetaContactGroupImpl");
}
MetaContactImpl newMetaContact = new MetaContactImpl();
this.addNewContactToMetaContact(provider, metaContactGroup,
newMetaContact, contactID, false);
//don't fire a PROTO_CONT_ADDED event we'll
//fire our own event here.
fireMetaContactEvent( newMetaContact,
findParentMetaContactGroup(newMetaContact),
MetaContactEvent.META_CONTACT_ADDED);
return newMetaContact;
}
/**
* Creates a <tt>MetaContactGroup</tt> with the specified group name.
* The meta contact group would only be created locally and resolved
* against the different server stored protocol contact lists upon the
* creation of the first protocol specific child contact in the respective
* group.
*
* @param parent
* the meta contact group inside which the new child group must
* be created.
* @param groupName the name of the <tt>MetaContactGroup</tt> to create.
* @return the newly created <tt>MetaContactGroup</tt>
*
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
public MetaContactGroup createMetaContactGroup(MetaContactGroup parent,
String groupName)
throws MetaContactListException
{
if (! (parent instanceof MetaContactGroupImpl))
{
throw new IllegalArgumentException(
parent
+ " is not an instance of MetaContactGroupImpl");
}
//make sure that "parent" does not already contain a subgroup called
//"groupName"
Iterator<MetaContactGroup> subgroups = parent.getSubgroups();
while(subgroups.hasNext())
{
MetaContactGroup group = subgroups.next();
if(group.getGroupName().equals(groupName))
{
throw new MetaContactListException(
"Parent " + parent.getGroupName() + " already contains a "
+ "group called " + groupName,
new CloneNotSupportedException("just testing nested exc-s"),
MetaContactListException.CODE_GROUP_ALREADY_EXISTS_ERROR);
}
}
// we only have to create the meta contact group here.
// we don't care about protocol specific groups.
MetaContactGroupImpl newMetaGroup
= new MetaContactGroupImpl(this, groupName);
( (MetaContactGroupImpl) parent).addSubgroup(newMetaGroup);
//fire the event
fireMetaContactGroupEvent(newMetaGroup
, null, null, MetaContactGroupEvent. META_CONTACT_GROUP_ADDED);
return newMetaGroup;
}
/**
* Renames the specified <tt>MetaContactGroup</tt> as indicated by the
* <tt>newName</tt> param.
* The operation would only affect the local meta group and would not
* "touch" any encapsulated protocol specific group.
* <p>
* @param group the group to rename.
* @param newGroupName the new name of the <tt>MetaContactGroup</tt> to
* rename.
*/
public void renameMetaContactGroup(MetaContactGroup group,
String newGroupName)
{
( (MetaContactGroupImpl) group).setGroupName(newGroupName);
Iterator<ContactGroup> groups = group.getContactGroups();
while (groups.hasNext())
{
ContactGroup protoGroup = groups.next();
//get a persistent presence operation set
OperationSetPersistentPresence opSetPresence
= protoGroup
.getProtocolProvider()
.getOperationSet(
OperationSetPersistentPresence.class);
if (opSetPresence != null)
{
try
{
opSetPresence.renameServerStoredContactGroup(
protoGroup, newGroupName);
}
catch(Throwable t)
{
logger.error("Error renaming protocol group: "
+ protoGroup, t);
}
}
}
fireMetaContactGroupEvent(group, null, null
, MetaContactGroupEvent.META_CONTACT_GROUP_RENAMED);
}
/**
* Returns the root <tt>MetaContactGroup</tt> in this contact list.
*
* @return the root <tt>MetaContactGroup</tt> for this contact list.
*/
public MetaContactGroup getRoot()
{
return rootMetaGroup;
}
/**
* Sets the display name for <tt>metaContact</tt> to be <tt>newName</tt>.
* <p>
* @param metaContact the <tt>MetaContact</tt> that we are renaming
* @param newDisplayName a <tt>String</tt> containing the new display name
* for <tt>metaContact</tt>.
* @throws IllegalArgumentException if <tt>metaContact</tt> is not an
* instance that belongs to the underlying implementation.
*/
public void renameMetaContact(MetaContact metaContact, String newDisplayName)
throws IllegalArgumentException
{
renameMetaContact(metaContact, newDisplayName, true);
}
/**
* Sets the display name for <tt>metaContact</tt> to be <tt>newName</tt>.
* <p>
* @param metaContact the <tt>MetaContact</tt> that we are renaming
* @param newDisplayName a <tt>String</tt> containing the new display name
* for <tt>metaContact</tt>.
* @throws IllegalArgumentException if <tt>metaContact</tt> is not an
* instance that belongs to the underlying implementation.
*/
private void renameMetaContact(MetaContact metaContact,
String newDisplayName,
boolean isUserDefined)
throws IllegalArgumentException
{
if (! (metaContact instanceof MetaContactImpl))
{
throw new IllegalArgumentException(
metaContact + " is not a MetaContactImpl instance.");
}
String oldDisplayName = metaContact.getDisplayName();
((MetaContactImpl)metaContact).setDisplayName(newDisplayName);
if(isUserDefined)
((MetaContactImpl)metaContact).setDisplayNameUserDefined(true);
Iterator<Contact> contacts = metaContact.getContacts();
while (contacts.hasNext())
{
Contact protoContact = contacts.next();
//get a persistent presence operation set
OperationSetPersistentPresence opSetPresence
= protoContact
.getProtocolProvider()
.getOperationSet(
OperationSetPersistentPresence.class);
if (opSetPresence != null)
{
try
{
opSetPresence.setDisplayName(protoContact, newDisplayName);
}
catch(Throwable t)
{
logger.error("Error renaming protocol contact: "
+ protoContact, t);
}
}
}
fireMetaContactEvent(new MetaContactRenamedEvent(
metaContact, oldDisplayName, newDisplayName));
//changing the display name has surely brought a change in the order as
//well so let's tell the others
fireMetaContactGroupEvent(
findParentMetaContactGroup( metaContact )
, null
, null
, MetaContactGroupEvent.CHILD_CONTACTS_REORDERED);
}
/**
* Resets display name of the MetaContact to show the value from
* the underlying contacts.
* @param metaContact the <tt>MetaContact</tt> that we are operating on
* @throws IllegalArgumentException if <tt>metaContact</tt> is not an
* instance that belongs to the underlying implementation.
*/
public void clearUserDefinedDisplayName(MetaContact metaContact)
throws IllegalArgumentException
{
if (! (metaContact instanceof MetaContactImpl))
{
throw new IllegalArgumentException(
metaContact + " is not a MetaContactImpl instance.");
}
// set display name
((MetaContactImpl)metaContact).setDisplayNameUserDefined(false);
if(metaContact.getContactCount() == 1)
{
renameMetaContact(metaContact,
metaContact.getDefaultContact().getDisplayName(), false);
}
else
{
// just fire event so the modification is stored
fireMetaContactEvent(new MetaContactRenamedEvent(
metaContact,
metaContact.getDisplayName(),
metaContact.getDisplayName()));
}
}
/**
* Sets the avatar for <tt>metaContact</tt> to be <tt>newAvatar</tt>.
* <p>
* @param metaContact the <tt>MetaContact</tt> that change avatar
* @param protoContact the <tt>Contact> that change avatar
* @param newAvatar avatar image bytes
* @throws IllegalArgumentException if <tt>metaContact</tt> is not an
* instance that belongs to the underlying implementation.
*/
public void changeMetaContactAvatar(MetaContact metaContact,
Contact protoContact,
byte[] newAvatar)
throws IllegalArgumentException
{
if (! (metaContact instanceof MetaContactImpl))
{
throw new IllegalArgumentException(
metaContact + " is not a MetaContactImpl instance.");
}
byte[] oldAvatar = metaContact.getAvatar(true);
((MetaContactImpl) metaContact).cacheAvatar(protoContact, newAvatar);
fireMetaContactEvent(
new MetaContactAvatarUpdateEvent(metaContact, oldAvatar, newAvatar));
}
/**
* Makes the specified <tt>contact</tt> a child of the
* <tt>newParentMetaGroup</tt> MetaContactGroup. If <tt>contact</tt> was
* previously a child of a meta contact, it will be removed from its
* old parent and to a newly created one even if they both are in the same
* group. If the specified contact was the only child of its previous
* parent, then the meta contact will also be moved.
*
*
* @param contact the <tt>Contact</tt> to move to the
* @param newParentMetaGroup the MetaContactGroup where we'd like contact to
* be moved.
* @throws MetaContactListException with an appropriate code if the
* operation fails for some reason.
*/
public void moveContact(Contact contact,
MetaContactGroup newParentMetaGroup)
throws MetaContactListException
{
if(contact.getPersistableAddress() == null)
{
logger.info("Contact cannot be moved! This contact doesn't have " +
"persistant address.");
return;
}
if(contact.getPersistableAddress() == null)
{
logger.info("Contact cannot be moved! This contact doesn't have " +
"persistant address.");
return;
}
ProtocolProviderService provider = contact.getProtocolProvider();
OperationSetMultiUserChat opSetMUC
= provider.getOperationSet(OperationSetMultiUserChat.class);
if(opSetMUC != null
&& opSetMUC.isPrivateMessagingContact(contact.getAddress()))
{
MetaContactImpl metaContactImpl = new MetaContactImpl();
MetaContactGroupImpl newParentMetaGroupImpl
= (MetaContactGroupImpl)newParentMetaGroup;
newParentMetaGroupImpl.addMetaContact(metaContactImpl);
fireMetaContactEvent(metaContactImpl
, newParentMetaGroupImpl
, MetaContactEvent.META_CONTACT_ADDED);
addNewContactToMetaContact(provider, metaContactImpl,
contact.getPersistableAddress());
return;
}
//get a persistent presence operation set
OperationSetPersistentPresence opSetPresence
= provider.getOperationSet(OperationSetPersistentPresence.class);
if (opSetPresence == null)
{
/** @todo handle non persistent presence operation sets */
}
MetaContactImpl currentParentMetaContact
= (MetaContactImpl)this.findMetaContactByContact(contact);
ContactGroup parentProtoGroup = resolveProtoPath(contact
.getProtocolProvider(), (MetaContactGroupImpl) newParentMetaGroup);
//if the contact is not currently in the proto group corresponding to
//its new metacontact group parent then move it
try
{
if(contact.getParentContactGroup() != parentProtoGroup
&& opSetPresence != null)
{
opSetPresence.moveContactToGroup(contact, parentProtoGroup);
}
// remove the proto-contact only if move is successful
currentParentMetaContact.removeProtoContact(contact);
}
catch(OperationFailedException ex)
{
throw new MetaContactListException(ex.getMessage(),
MetaContactListException.CODE_MOVE_CONTACT_ERROR);
}
// first check if this has been already done on other place
// (SubscriptionListener.subscriptionMoved)
MetaContactImpl metaContactImpl = null;
synchronized(contact)
{
MetaContact checkContact = findMetaContactByContact(contact);
if(checkContact == null)
{
metaContactImpl = new MetaContactImpl();
((MetaContactGroupImpl)newParentMetaGroup)
.addMetaContact(metaContactImpl);
metaContactImpl.addProtoContact(contact);
}
}
if(metaContactImpl != null)
{
fireMetaContactEvent(metaContactImpl
, newParentMetaGroup
, MetaContactEvent.META_CONTACT_ADDED);
//fire an event telling everyone that contact has been added to its
//new parent.
fireProtoContactEvent(
contact,
ProtoContactEvent.PROTO_CONTACT_MOVED,
currentParentMetaContact,
metaContactImpl);
}
//if this was the last contact in the meta contact - remove it.
//it is true that in some cases the move would be followed by some kind
//of protocol provider events indicating the change which on its turn
//may trigger the removal of empty meta contacts. Yet in many cases
//particularly if parent groups were not changed in the protocol contact
//list no event would come and the meta contact will remain empty
//that's why we delete it here and if an event follows it would simply
//be ignored.
if (currentParentMetaContact.getContactCount() == 0)
{
MetaContactGroupImpl parentMetaGroup =
currentParentMetaContact.getParentGroup();
parentMetaGroup.removeMetaContact(currentParentMetaContact);
fireMetaContactEvent(currentParentMetaContact, parentMetaGroup
, MetaContactEvent.META_CONTACT_REMOVED);
}
}
/**
* Makes the specified <tt>contact</tt> a child of the <tt>newParent</tt>
* MetaContact.
*
* @param contact
* the <tt>Contact</tt> to move to the
* @param newParentMetaContact
* the MetaContact where we'd like contact to be moved.
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
public void moveContact(Contact contact,
MetaContact newParentMetaContact) throws
MetaContactListException
{
if(contact.getPersistableAddress() == null)
{
logger.info("Contact cannot be moved! This contact doesn't have " +
"persistant address.");
return;
}
ProtocolProviderService provider = contact.getProtocolProvider();
OperationSetMultiUserChat opSetMUC
= provider.getOperationSet(OperationSetMultiUserChat.class);
if(opSetMUC != null
&& opSetMUC.isPrivateMessagingContact(contact.getAddress()))
{
addNewContactToMetaContact(provider, newParentMetaContact,
contact.getPersistableAddress());
return;
}
//get a persistent presence operation set
OperationSetPersistentPresence opSetPresence
= provider.getOperationSet(OperationSetPersistentPresence.class);
if (opSetPresence == null)
{
/** @todo handle non persistent presence operation sets */
}
if (! (newParentMetaContact instanceof MetaContactImpl))
{
throw new IllegalArgumentException(
newParentMetaContact + " is not a MetaContactImpl instance.");
}
MetaContactImpl currentParentMetaContact
= (MetaContactImpl)this.findMetaContactByContact(contact);
MetaContactGroup newParentGroup
= findParentMetaContactGroup(newParentMetaContact);
ContactGroup parentProtoGroup = resolveProtoPath(contact
.getProtocolProvider(), (MetaContactGroupImpl) newParentGroup);
//if the contact is not currently in the proto group corresponding to
//its new metacontact group parent then move it
try
{
if(contact.getParentContactGroup() != parentProtoGroup
&& opSetPresence != null)
{
opSetPresence.moveContactToGroup(contact, parentProtoGroup);
}
// remove the proto-contact only if move is successful
currentParentMetaContact.removeProtoContact(contact);
}
catch(OperationFailedException ex)
{
throw new MetaContactListException(ex.getMessage(),
MetaContactListException.CODE_MOVE_CONTACT_ERROR);
}
synchronized(contact)
{
MetaContact checkContact = findMetaContactByContact(contact);
if(checkContact == null)
{
( (MetaContactImpl) newParentMetaContact)
.addProtoContact(contact);
}
}
if(newParentMetaContact.containsContact(contact))
{
//fire an event telling everyone that contact has been added to its
//new parent.
fireProtoContactEvent(contact, ProtoContactEvent.PROTO_CONTACT_MOVED
, currentParentMetaContact , newParentMetaContact);
}
//if this was the last contact in the meta contact - remove it.
//it is true that in some cases the move would be followed by some kind
//of protocol provider events indicating the change which on its turn
//may trigger the removal of empty meta contacts. Yet in many cases
//particularly if parent groups were not changed in the protocol contact
//list no event would come and the meta contact will remain empty
//that's why we delete it here and if an event follows it would simply
//be ignored.
if (currentParentMetaContact.getContactCount() == 0)
{
MetaContactGroupImpl parentMetaGroup =
currentParentMetaContact.getParentGroup();
parentMetaGroup.removeMetaContact(currentParentMetaContact);
fireMetaContactEvent(currentParentMetaContact, parentMetaGroup
, MetaContactEvent.META_CONTACT_REMOVED);
}
}
/**
* Moves the specified <tt>MetaContact</tt> to <tt>newGroup</tt>.
*
* @param metaContact
* the <tt>MetaContact</tt> to move.
* @param newMetaGroup
* the <tt>MetaContactGroup</tt> that should be the new parent
* of <tt>contact</tt>.
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
* @throws IllegalArgumentException if <tt>newMetaGroup</tt> or
* <tt>metaCOntact</tt> do not come from this implementation.
*/
public void moveMetaContact(MetaContact metaContact,
MetaContactGroup newMetaGroup) throws
MetaContactListException, IllegalArgumentException
{
if (! (newMetaGroup instanceof MetaContactGroupImpl))
{
throw new IllegalArgumentException(newMetaGroup
+
" is not a MetaContactGroupImpl instance");
}
if (! (metaContact instanceof MetaContactImpl))
{
throw new IllegalArgumentException(metaContact
+
" is not a MetaContactImpl instance");
}
//first remove the meta contact from its current parent:
MetaContactGroupImpl currentParent
= (MetaContactGroupImpl) findParentMetaContactGroup(metaContact);
if (currentParent != null)
currentParent.removeMetaContact( (MetaContactImpl) metaContact);
( (MetaContactGroupImpl) newMetaGroup).addMetaContact(
(MetaContactImpl) metaContact);
try
{
//first make sure that the new meta contact group path is resolved
//against all protocols that the MetaContact requires. then move
//the meta contact in there and move all prot contacts inside it.
Iterator<Contact> contacts = metaContact.getContacts();
while (contacts.hasNext())
{
Contact protoContact = contacts.next();
ContactGroup protoGroup = resolveProtoPath(protoContact
.getProtocolProvider(), (MetaContactGroupImpl) newMetaGroup);
//get a persistent or non persistent presence operation set
OperationSetPersistentPresence opSetPresence
= protoContact
.getProtocolProvider()
.getOperationSet(
OperationSetPersistentPresence.class);
if (opSetPresence == null)
{
/** @todo handle non persistent presence operation sets */
}
else
{
if(newMetaGroup.equals(getRoot()))
opSetPresence.moveContactToGroup(
protoContact,
opSetPresence.getServerStoredContactListRoot());
else
opSetPresence.moveContactToGroup(
protoContact, protoGroup);
}
}
}
catch (Exception ex)
{
logger.error("Cannot move contact", ex);
// now move the contact to prevoius parent
((MetaContactGroupImpl)newMetaGroup).
removeMetaContact( (MetaContactImpl) metaContact);
currentParent.addMetaContact((MetaContactImpl) metaContact);
throw new MetaContactListException(ex.getMessage(),
MetaContactListException.CODE_MOVE_CONTACT_ERROR);
}
//fire the mved event.
fireMetaContactEvent(new MetaContactMovedEvent(
metaContact, currentParent, newMetaGroup));
}
/**
* Deletes the specified contact from both the local contact list and (if
* applicable) the server stored contact list if supported by the
* corresponding protocol.
*
* @param contact the contact to remove.
* @throws MetaContactListException with an appropriate code if the
* operation fails for some reason.
*/
public void removeContact(Contact contact) throws MetaContactListException
{
//remove the contact from the provider and do nothing else
//updating and/or removing the corresponding meta contact would happen
//once a confirmation event is received from the underlying protocol
//provider
OperationSetPresence opSetPresence
= contact
.getProtocolProvider()
.getOperationSet(OperationSetPresence.class);
//in case the provider only has a persistent operation set:
if (opSetPresence == null)
{
opSetPresence =
contact.getProtocolProvider()
.getOperationSet(OperationSetPersistentPresence.class);
if (opSetPresence == null)
{
throw new IllegalStateException(
"Cannot remove a contact from a provider with no presence "
+ "operation set.");
}
}
try
{
opSetPresence.unsubscribe(contact);
}
catch (Exception ex)
{
String errorTxt = "Failed to remove "
+ contact + " from its protocol provider. ";
if(ex instanceof OperationFailedException)
errorTxt += ex.getMessage();
throw new MetaContactListException(errorTxt,
ex,
MetaContactListException.
CODE_NETWORK_ERROR);
}
}
/**
* Removes a listener previously added with <tt>addContactListListener</tt>.
*
* @param listener the listener to remove
*/
public void removeMetaContactListListener(
MetaContactListListener listener)
{
synchronized (metaContactListListeners)
{
this.metaContactListListeners.remove(listener);
}
}
/**
* Removes the specified <tt>metaContact</tt> as well as all of its
* underlying contacts.
*
* @param metaContact
* the metaContact to remove.
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
public void removeMetaContact(MetaContact metaContact) throws
MetaContactListException
{
Iterator<Contact> protoContactsIter = metaContact.getContacts();
while (protoContactsIter.hasNext())
{
removeContact( protoContactsIter.next());
}
//do not fire events. that will be done by the contact listener as soon
//as it gets confirmation events of proto contact removal
//the removal of the last contact would also generate an even for the
//removal of the meta contact itself.
}
/**
* Removes the specified meta contact group, all its corresponding protocol
* specific groups and all their children.
*
* @param groupToRemove
* the <tt>MetaContactGroup</tt> to have removed.
* @throws MetaContactListException
* with an appropriate code if the operation fails for some
* reason.
*/
public void removeMetaContactGroup(
MetaContactGroup groupToRemove) throws MetaContactListException
{
if (! (groupToRemove instanceof MetaContactGroupImpl))
{
throw new IllegalArgumentException(
groupToRemove
+ " is not an instance of MetaContactGroupImpl");
}
try
{
//remove all proto groups and then remove the meta group as well.
Iterator<ContactGroup> protoGroups
= groupToRemove.getContactGroups();
while (protoGroups.hasNext())
{
ContactGroup protoGroup = protoGroups.next();
OperationSetPersistentPresence opSetPersPresence
= protoGroup
.getProtocolProvider()
.getOperationSet(
OperationSetPersistentPresence.class);
if (opSetPersPresence == null)
{
/** @todo handle removal of non persistent proto groups */
return;
}
opSetPersPresence.removeServerStoredContactGroup(protoGroup);
}
}
catch(Exception ex)
{
throw new MetaContactListException(ex.getMessage(),
MetaContactListException.CODE_REMOVE_GROUP_ERROR);
}
MetaContactGroupImpl parentMetaGroup = (MetaContactGroupImpl)
findParentMetaContactGroup(groupToRemove);
parentMetaGroup.removeSubgroup(groupToRemove);
fireMetaContactGroupEvent( groupToRemove, null, null
, MetaContactGroupEvent.META_CONTACT_GROUP_REMOVED);
}
/**
* Removes the protocol specific group from the specified meta contact group
* and removes from meta contacts all proto contacts that belong to the
* same provider as the group which is being removed.
* @param metaContainer the MetaContactGroup that we'd like to remove a
* contact group from.
* @param groupToRemove the ContactGroup that we'd like removed.
* @param sourceProvider the ProtocolProvider that the contact group belongs
* to.
*/
public void removeContactGroupFromMetaContactGroup(
MetaContactGroupImpl metaContainer,
ContactGroup groupToRemove,
ProtocolProviderService sourceProvider)
{
// if we failed to find the metagroup corresponding to proto group
if(metaContainer == null)
{
logger.warn(
"No meta container found, when trying to remove group: "
+ groupToRemove);
return;
}
/*
* Go through all meta contacts and remove all contacts that belong to
* the same provider and are therefore children of the group that is
* being removed.
*/
locallyRemoveAllContactsForProvider(metaContainer, groupToRemove);
fireMetaContactGroupEvent(
metaContainer,
sourceProvider,
groupToRemove,
MetaContactGroupEvent.CONTACT_GROUP_REMOVED_FROM_META_GROUP);
}
/**
* Removes local resources storing copies of the meta contact list. This
* method is meant primarily to aid automated testing which may depend on
* beginning the tests with an empty local contact list.
*/
public void purgeLocallyStoredContactListCopy()
{
this.storageManager.storeContactListAndStopStorageManager();
this.storageManager.removeContactListFile();
if (logger.isTraceEnabled())
logger.trace("Removed meta contact list storage file.");
}
/**
* Goes through the specified group and removes from all meta contacts,
* protocol specific contacts belonging to the specified
* <tt>groupToRemove</tt>. Note that this method won't undertake any calls
* to the protocol itself as it is used only to update the local contact
* list as a result of a server generated event.
*
* @param parentMetaGroup the MetaContactGroup whose children we should go
* through
* @param groupToRemove the proto group that we want removed together with
* its children.
*/
private void locallyRemoveAllContactsForProvider(
MetaContactGroupImpl parentMetaGroup,
ContactGroup groupToRemove)
{
Iterator<MetaContact> childrenContactsIter
= parentMetaGroup.getChildContacts();
//first go through all direct children.
while (childrenContactsIter.hasNext())
{
MetaContactImpl child
= (MetaContactImpl) childrenContactsIter.next();
//Get references to all contacts that will be removed in case we
//need to fire an event.
Iterator<Contact> contactsToRemove
= child.getContactsForContactGroup(groupToRemove);
child.removeContactsForGroup(groupToRemove);
//if this was the last proto contact inside this meta contact,
//then remove the meta contact as well. Otherwise only fire an
//event.
if (child.getContactCount() == 0)
{
parentMetaGroup.removeMetaContact(child);
fireMetaContactEvent(child, parentMetaGroup
, MetaContactEvent.META_CONTACT_REMOVED);
}
else
{
// there are other proto contacts left in the contact child
//meta contact so we'll have to send an event for each of the
//removed contacts and not only a single event for the whole
//meta contact.
while (contactsToRemove.hasNext())
{
fireProtoContactEvent(
contactsToRemove.next()
, ProtoContactEvent.PROTO_CONTACT_REMOVED
, child
, null);
}
}
}
Iterator<MetaContactGroup> subgroupsIter
= parentMetaGroup.getSubgroups();
//then go through all subgroups.
while (subgroupsIter.hasNext())
{
MetaContactGroupImpl subMetaGroup
= (MetaContactGroupImpl) subgroupsIter.next();
Iterator<ContactGroup> contactGroups
= subMetaGroup.getContactGroups();
ContactGroup protoGroup = null;
while(contactGroups.hasNext())
{
protoGroup = contactGroups.next();
if(groupToRemove == protoGroup.getParentContactGroup())
this.locallyRemoveAllContactsForProvider(
subMetaGroup, protoGroup);
}
//remove the group if there are no children left.
if(subMetaGroup.countSubgroups() == 0
&& subMetaGroup.countChildContacts() == 0
&& subMetaGroup.countContactGroups() == 0)
{
parentMetaGroup.removeSubgroup(subMetaGroup);
fireMetaContactGroupEvent(
subMetaGroup
, groupToRemove.getProtocolProvider()
, protoGroup
, MetaContactGroupEvent.META_CONTACT_GROUP_REMOVED);
}
}
parentMetaGroup.removeProtoGroup(groupToRemove);
}
/**
* Returns the MetaContactGroup corresponding to the specified contactGroup
* or null if no such MetaContactGroup was found.
* @return the MetaContactGroup corresponding to the specified contactGroup
* or null if no such MetaContactGroup was found.
* @param contactGroup
* the protocol specific <tt>contactGroup</tt> that we're looking
* for.
*/
public MetaContactGroup findMetaContactGroupByContactGroup
(ContactGroup contactGroup)
{
return rootMetaGroup.findMetaContactGroupByContactGroup(contactGroup);
}
/**
* Returns the MetaContact containing the specified contact or null if no
* such MetaContact was found. The method can be used when for example we
* need to find the MetaContact that is the author of an incoming message
* and the corresponding ProtocolProviderService has only provided a
* <tt>Contact</tt> as its author.
*
* @param contact the protocol specific <tt>contact</tt> that we're looking
* for.
*
* @return the MetaContact containing the specified contact or null if no
* such contact is present in this contact list.
*/
public MetaContact findMetaContactByContact(Contact contact)
{
return rootMetaGroup.findMetaContactByContact(contact);
}
/**
* Returns the MetaContact containing a contact with an address equal to
* <tt>contactAddress</tt> and with a source provider matching
* <tt>accountID</tt>, or null if no such MetaContact was found. The method
* can be used when for example we
* need to find the MetaContact that is the author of an incoming message
* and the corresponding ProtocolProviderService has only provided a
* <tt>Contact</tt> as its author.
*
* @param contactAddress the address of the protocol specific
* <tt>contact</tt> that we're looking for.
* @param accountID the ID of the account that the contact we're looking for
* must belong to.
*
* @return the MetaContact containing the specified contact or null if no
* such contact is present in this contact list.
*/
public MetaContact findMetaContactByContact(String contactAddress,
String accountID)
{
return rootMetaGroup.findMetaContactByContact(contactAddress
, accountID);
}
/**
* Returns the MetaContact that corresponds to the specified metaContactID.
*
* @param metaContactID
* a String identifier of a meta contact.
* @return the MetaContact with the specified string identifier or null if
* no such meta contact was found.
*/
public MetaContact findMetaContactByMetaUID(String metaContactID)
{
return rootMetaGroup.findMetaContactByMetaUID(metaContactID);
}
/**
* Returns the MetaContactGroup that corresponds to the specified
* metaGroupID.
*
* @param metaGroupID
* a String identifier of a meta contact group.
* @return the MetaContactGroup with the specified string identifier or null
* if no such meta contact was found.
*/
public MetaContactGroup findMetaContactGroupByMetaUID(String metaGroupID)
{
return rootMetaGroup.findMetaContactGroupByMetaUID(metaGroupID);
}
/**
* Returns a list of all <tt>MetaContact</tt>s containing a protocol contact
* from the given <tt>ProtocolProviderService</tt>.
*
* @param protocolProvider the <tt>ProtocolProviderService</tt> whose
* contacts we're looking for.
* @return a list of all <tt>MetaContact</tt>s containing a protocol contact
* from the given <tt>ProtocolProviderService</tt>.
*/
public Iterator<MetaContact> findAllMetaContactsForProvider(
ProtocolProviderService protocolProvider)
{
List<MetaContact> resultList = new ArrayList<MetaContact>();
this.findAllMetaContactsForProvider(protocolProvider,
rootMetaGroup,
resultList);
return resultList.iterator();
}
/**
* Returns a list of all <tt>MetaContact</tt>s contained in the given group
* and containing a protocol contact from the given
* <tt>ProtocolProviderService</tt>.
*
* @param protocolProvider the <tt>ProtocolProviderService</tt> whose
* contacts we're looking for.
* @param metaContactGroup the parent group.
*
* @return a list of all <tt>MetaContact</tt>s containing a protocol contact
* from the given <tt>ProtocolProviderService</tt>.
*/
public Iterator<MetaContact> findAllMetaContactsForProvider(
ProtocolProviderService protocolProvider,
MetaContactGroup metaContactGroup)
{
List<MetaContact> resultList = new LinkedList<MetaContact>();
this.findAllMetaContactsForProvider(protocolProvider,
metaContactGroup, resultList);
return resultList.iterator();
}
/**
* Returns a list of all <tt>MetaContact</tt>s containing a protocol contact
* corresponding to the given <tt>contactAddress</tt> string.
*
* @param contactAddress the contact address for which we're looking for
* a parent <tt>MetaContact</tt>.
* @return a list of all <tt>MetaContact</tt>s containing a protocol contact
* corresponding to the given <tt>contactAddress</tt> string.
*/
public Iterator<MetaContact> findAllMetaContactsForAddress(
String contactAddress)
{
List<MetaContact> resultList = new LinkedList<MetaContact>();
findAllMetaContactsForAddress(rootMetaGroup, contactAddress, resultList);
return resultList.iterator();
}
/**
* Returns a list of all <tt>MetaContact</tt>s containing a protocol contact
* corresponding to the given <tt>contactAddress</tt> string.
*
* @param contactAddress the contact address for which we're looking for
* a parent <tt>MetaContact</tt>.
* @param metaContactGroup the parent group.
* @param resultList the list containing the result of the search.
*/
private void findAllMetaContactsForAddress(
MetaContactGroup metaContactGroup,
String contactAddress,
List<MetaContact> resultList)
{
Iterator<MetaContact> childContacts
= metaContactGroup.getChildContacts();
while (childContacts.hasNext())
{
MetaContact metaContact = childContacts.next();
Iterator<Contact> protocolContacts = metaContact.getContacts();
while (protocolContacts.hasNext())
{
Contact protocolContact = protocolContacts.next();
if (protocolContact.getAddress().equals(contactAddress)
|| protocolContact.getDisplayName().equals(contactAddress))
resultList.add(metaContact);
}
}
Iterator<MetaContactGroup> subGroups
= metaContactGroup.getSubgroups();
while (subGroups.hasNext())
{
MetaContactGroup subGroup = subGroups.next();
Iterator<ContactGroup> protocolSubgroups
= subGroup.getContactGroups();
if (protocolSubgroups.hasNext())
{
this.findAllMetaContactsForAddress( subGroup,
contactAddress,
resultList);
}
}
}
/**
* Returns a list of all <tt>MetaContact</tt>s contained in the given group
* and containing a protocol contact from the given
* <tt>ProtocolProviderService</tt>.
*
* @param protocolProvider the <tt>ProtocolProviderService</tt> whose
* contacts we're looking for.
* @param metaContactGroup the parent group.
* @param resultList the list containing the result of the search.
*/
private void findAllMetaContactsForProvider(
ProtocolProviderService protocolProvider,
MetaContactGroup metaContactGroup,
List<MetaContact> resultList)
{
Iterator<MetaContact> childContacts
= metaContactGroup.getChildContacts();
while (childContacts.hasNext())
{
MetaContact metaContact = childContacts.next();
Iterator<Contact> protocolContacts
= metaContact.getContactsForProvider(protocolProvider);
if (protocolContacts.hasNext())
{
resultList.add(metaContact);
}
}
Iterator<MetaContactGroup> subGroups
= metaContactGroup.getSubgroups();
while (subGroups.hasNext())
{
MetaContactGroup subGroup = subGroups.next();
Iterator<ContactGroup> protocolSubgroups
= subGroup.getContactGroupsForProvider(protocolProvider);
if (protocolSubgroups.hasNext())
{
this.findAllMetaContactsForProvider(protocolProvider,
subGroup,
resultList);
}
}
}
/**
* Goes through the server stored ContactList of the specified operation
* set, retrieves all protocol specific contacts it contains and makes sure
* they are all present in the local contact list.
*
* @param presenceOpSet
* the presence operation set whose contact list we'd like to
* synchronize with the local contact list.
*/
private void synchronizeOpSetWithLocalContactList(
OperationSetPersistentPresence presenceOpSet)
{
ContactGroup rootProtoGroup = presenceOpSet
.getServerStoredContactListRoot();
if (rootProtoGroup != null)
{
if (logger.isTraceEnabled())
logger.trace("subgroups: "
+ rootProtoGroup.countSubgroups());
if (logger.isTraceEnabled())
logger.trace("child contacts: "
+ rootProtoGroup.countContacts());
addContactGroupToMetaGroup(rootProtoGroup, rootMetaGroup, true);
}
presenceOpSet
.addSubscriptionListener(clSubscriptionEventHandler);
presenceOpSet
.addServerStoredGroupChangeListener(clGroupEventHandler);
}
/**
* Creates meta contacts and meta contact groups for all children of the
* specified <tt>contactGroup</tt> and adds them to <tt>metaGroup</tt>
* @param protoGroup the <tt>ContactGroup</tt> to add.
* <p>
* @param metaGroup the <tt>MetaContactGroup</tt> where <tt>ContactGroup</tt>
* should be added.
* @param fireEvents indicates whether or not events are to be fired upon
* adding subcontacts and subgroups. When this method is called recursively,
* the parameter should will be false in order to generate a minimal number
* of events for the whole addition and not an event per every subgroup
* and child contact.
*/
private void addContactGroupToMetaGroup(ContactGroup protoGroup,
MetaContactGroupImpl metaGroup,
boolean fireEvents)
{
// first register the root group
metaGroup.addProtoGroup(protoGroup);
// register subgroups and contacts
Iterator<ContactGroup> subgroupsIter = protoGroup.subgroups();
while (subgroupsIter.hasNext())
{
ContactGroup group = subgroupsIter.next();
//continue if we have already loaded this group from the locally
//stored contact list.
if(metaGroup.findMetaContactGroupByContactGroup(group) != null)
continue;
// right now we simply map this group to an existing one
// without being cautious and verify whether we already have it
// registered
MetaContactGroupImpl newMetaGroup
= new MetaContactGroupImpl(this, group.getGroupName());
metaGroup.addSubgroup(newMetaGroup);
addContactGroupToMetaGroup(group, newMetaGroup, false);
if (fireEvents)
{
this.fireMetaContactGroupEvent(
newMetaGroup
, group.getProtocolProvider()
, group
, MetaContactGroupEvent.
META_CONTACT_GROUP_ADDED);
}
}
// now add all contacts, located in this group
Iterator<Contact> contactsIter = protoGroup.contacts();
while (contactsIter.hasNext())
{
Contact contact = contactsIter.next();
//continue if we have already loaded this contact from the locally
//stored contact list.
if(metaGroup.findMetaContactByContact(contact) != null)
continue;
MetaContactImpl newMetaContact = new MetaContactImpl();
newMetaContact.addProtoContact(contact);
metaGroup.addMetaContact(newMetaContact);
if (fireEvents)
{
this.fireMetaContactEvent(newMetaContact,
metaGroup,
MetaContactEvent.META_CONTACT_ADDED);
}
}
}
/**
* Adds the specified provider to the list of currently known providers. In
* case the provider supports persistent presence the method would also
* extract all contacts and synchronize them with the local contact list.
* Otherwise it would start a process where local contacts would be added on
* the server.
*
* @param provider the ProtocolProviderService that we've just detected.
*/
private synchronized void handleProviderAdded(
ProtocolProviderService provider)
{
if (logger.isDebugEnabled())
logger.debug("Adding protocol provider "
+ provider.getAccountID().getAccountUniqueID());
// check whether the provider has a persistent presence op set
OperationSetPersistentPresence opSetPersPresence =
provider.getOperationSet(OperationSetPersistentPresence.class);
this.currentlyInstalledProviders.put(
provider.getAccountID().getAccountUniqueID(),
provider);
//If we have a persistent presence op set - then retrieve its contact
//list and merge it with the local one.
if (opSetPersPresence != null)
{
//load contacts, stored in the local contact list and corresponding to
//this provider.
try
{
storageManager.extractContactsForAccount(
provider.getAccountID().getAccountUniqueID());
if (logger.isDebugEnabled())
logger.debug("All contacts loaded for account "
+ provider.getAccountID().getAccountUniqueID());
}
catch (XMLException exc)
{
logger.error("Failed to load contacts for account "
+ provider.getAccountID().getAccountUniqueID(), exc);
}
synchronizeOpSetWithLocalContactList(opSetPersPresence);
}
else
{
if (logger.isDebugEnabled())
logger.debug("Service did not have a pers. pres. op. set.");
}
/** @todo implement handling non persistent presence operation sets */
//add a presence status listener so that we could reorder contacts upon
//status change. NOTE that we MUST NOT add the presence listener before
//extracting the locally stored contact list or otherwise we'll get
//events for all contacts that we have already extracted
if(opSetPersPresence != null)
opSetPersPresence.addContactPresenceStatusListener(this);
// Check if the capabilities operation set is available for this
// contact and add a listener to it in order to track capabilities'
// changes for all contained protocol contacts.
OperationSetContactCapabilities capOpSet
= provider.getOperationSet(OperationSetContactCapabilities.class);
if (capOpSet != null)
{
capOpSet.addContactCapabilitiesListener(this);
}
}
/**
* Removes the specified provider from the list of currently known providers
* and ignores all the contacts that it has registered locally.
*
* @param provider
* the ProtocolProviderService that has been unregistered.
*/
private void handleProviderRemoved(
ProtocolProviderService provider)
{
if (logger.isDebugEnabled())
logger.debug("Removing protocol provider " + provider);
this.currentlyInstalledProviders.
remove(provider.getAccountID().getAccountUniqueID());
//get the root group for the provider so that we could remove it.
OperationSetPersistentPresence persPresOpSet =
provider.getOperationSet(OperationSetPersistentPresence.class);
//ignore if persistent presence is not supported.
if(persPresOpSet != null)
{
//we don't care about subscription and presence status events here
// any longer.
persPresOpSet.removeContactPresenceStatusListener(this);
persPresOpSet.removeSubscriptionListener(
clSubscriptionEventHandler);
persPresOpSet.removeServerStoredGroupChangeListener(
clGroupEventHandler);
ContactGroup rootGroup
= persPresOpSet.getServerStoredContactListRoot();
//iterate all sub groups and remove them one by one
//(we dont simply remove the root group because the mcl storage
// manager is stupid (i wrote it) and doesn't know root groups exist.
// that's why it needs to hear an event for every single group.)
Iterator<ContactGroup> subgroups = rootGroup.subgroups();
while(subgroups.hasNext())
{
ContactGroup group = subgroups.next();
//remove the group
this.removeContactGroupFromMetaContactGroup(
(MetaContactGroupImpl) findMetaContactGroupByContactGroup(
group),
group,
provider);
}
//remove the root group
this.removeContactGroupFromMetaContactGroup(
this.rootMetaGroup, rootGroup, provider);
}
// Check if the capabilities operation set is available for this
// contact and remove previously added listeners.
OperationSetContactCapabilities capOpSet
= provider.getOperationSet(OperationSetContactCapabilities.class);
if (capOpSet != null)
capOpSet.removeContactCapabilitiesListener(this);
}
/**
* Registers <tt>group</tt> to the event ignore list. This would make the
* method that is normally handling events for newly created groups ignore
* any events for that particular group and leave the responsibility to the
* method that added the group to the ignore list.
*
* @param group the name of the group that we'd like to
* register.
* @param ownerProvider the protocol provider that we expect the addition
* to come from.
*/
private void addGroupToEventIgnoreList(
String group,
ProtocolProviderService ownerProvider)
{
//first check whether registrations in the ignore list already
//exist for this group.
if (isGroupInEventIgnoreList(group, ownerProvider))
{
return;
}
List<ProtocolProviderService> existingProvList
= this.groupEventIgnoreList.get(group);
if (existingProvList == null)
{
existingProvList = new LinkedList<ProtocolProviderService>();
}
existingProvList.add(ownerProvider);
groupEventIgnoreList.put(group, existingProvList);
}
/**
* Verifies whether the specified group is in the group event ignore list.
* @return true if the group is in the group event ignore list and false
* otherwise.
* @param group the group whose presence in the ignore list we'd like to
* verify.
* @param ownerProvider the provider that <tt>group</tt> belongs to.
*/
private boolean isGroupInEventIgnoreList(
String group, ProtocolProviderService ownerProvider)
{
List<ProtocolProviderService> existingProvList
= this.groupEventIgnoreList.get(group);
return existingProvList != null
&& existingProvList.contains(ownerProvider);
}
/**
* Removes the <tt>group</tt> from the group event ignore list so that
* events concerning this group get treated.
*
* @param group the group whose that we'd want out of the ignore list.
* @param ownerProvider the provider that <tt>group</tt> belongs to.
*/
private void removeGroupFromEventIgnoreList(
String group, ProtocolProviderService ownerProvider)
{
//first check whether the registration actually exists.
if (!isGroupInEventIgnoreList(group, ownerProvider))
{
return;
}
List<ProtocolProviderService> existingProvList
= this.groupEventIgnoreList.get(group);
if (existingProvList.size() < 1)
{
groupEventIgnoreList.remove(group);
}
else
{
existingProvList.remove(ownerProvider);
}
}
/**
* Registers <tt>contact</tt> to the event ignore list. This would make the
* method that is normally handling events for newly created contacts ignore
* any events for that particular contact and leave the responsibility to
* the method that added the contact to the ignore list.
*
* @param contact the address of the contact that we'd like to ignore.
* @param ownerProvider the protocol provider that we expect the addition
* to come from.
*/
private void addContactToEventIgnoreList(
String contact,
ProtocolProviderService ownerProvider)
{
//first check whether registrations in the ignore list already
//exist for this contact.
if (isContactInEventIgnoreList(contact, ownerProvider))
{
return;
}
List<ProtocolProviderService> existingProvList
= this.contactEventIgnoreList.get(contact);
if (existingProvList == null)
{
existingProvList = new LinkedList<ProtocolProviderService>();
}
existingProvList.add(ownerProvider);
contactEventIgnoreList.put(contact, existingProvList);
}
/**
* Verifies whether the specified contact is in the contact event ignore
* list.
* @return true if the contact is in the contact event ignore list and false
* otherwise.
* @param contact the contact whose presence in the ignore list we'd like to
* verify.
* @param ownerProvider the provider that <tt>contact</tt> belongs to.
*/
private boolean isContactInEventIgnoreList(
String contact, ProtocolProviderService ownerProvider)
{
List<ProtocolProviderService> existingProvList
= this.contactEventIgnoreList.get(contact);
return existingProvList != null
&& existingProvList.contains(ownerProvider);
}
/**
* Verifies whether the specified contact is in the contact event ignore
* list. The reason we need this method in addition to the one that takes a
* string contact address is necessary for the following reason: In some
* cases the ID that we create a contact with (e.g. mybuddy) could be
* different from the one returned by its getAddress() method (e.g.
* mybuddy@hisnet.com). If this is the case we hope that the difference
* would be handled gracefully in the equals method of the contact so
* we also compare with it.
*
* @return true if the contact is in the contact event ignore list and false
* otherwise.
* @param contact the contact whose presence in the ignore list we'd like to
* verify.
* @param ownerProvider the provider that <tt>contact</tt> belongs to.
*/
private boolean isContactInEventIgnoreList(
Contact contact,
ProtocolProviderService ownerProvider)
{
for (Map.Entry<String, List<ProtocolProviderService>> contactEventIgnoreEntry
: contactEventIgnoreList.entrySet())
{
String contactAddress = contactEventIgnoreEntry.getKey();
if(contact.getAddress().equals(contactAddress)
|| contact.equals(contactAddress))
{
List<ProtocolProviderService> existingProvList
= contactEventIgnoreEntry.getValue();
return existingProvList != null
&& existingProvList.contains(ownerProvider);
}
}
return false;
}
/**
* Removes the <tt>contact</tt> from the group event ignore list so that
* events concerning this group get treated.
*
* @param contact the contact whose that we'd want out of the ignore list.
* @param ownerProvider the provider that <tt>group</tt> belongs to.
*/
private void removeContactFromEventIgnoreList(
String contact, ProtocolProviderService ownerProvider)
{
//first check whether the registration actually exists.
if (!isContactInEventIgnoreList(contact, ownerProvider))
{
return;
}
List<ProtocolProviderService> existingProvList
= this.contactEventIgnoreList.get(contact);
if (existingProvList.size() < 1)
{
groupEventIgnoreList.remove(contact);
}
else
{
existingProvList.remove(ownerProvider);
}
}
/**
* Implements the <tt>ServiceListener</tt> method. Verifies whether the
* passed event concerns a <tt>ProtocolProviderService</tt> and modifies
* the list of registered protocol providers accordingly.
*
* @param event
* The <tt>ServiceEvent</tt> object.
*/
public void serviceChanged(ServiceEvent event)
{
Object sService = bundleContext.getService(event
.getServiceReference());
if (logger.isTraceEnabled())
logger.trace("Received a service event for: "
+ sService.getClass().getName());
// we don't care if the source service is not a protocol provider
if (! (sService instanceof ProtocolProviderService))
{
return;
}
if (logger.isDebugEnabled())
logger.debug("Service is a protocol provider.");
ProtocolProviderService provider =
(ProtocolProviderService)sService;
//first check if the event really means that the accounts is
//uninstalled/installed (or is it just stopped ... e.g. we could be
// shutting down, or in the other case it could be just modified) ...
// before that however, we'd need to get a reference to the service.
ProtocolProviderFactory sourceFactory = null;
ServiceReference<?>[] allBundleServices
= event.getServiceReference().getBundle().getRegisteredServices();
for (ServiceReference<?> bundleServiceRef : allBundleServices)
{
Object service = bundleContext.getService(bundleServiceRef);
if(service instanceof ProtocolProviderFactory)
{
sourceFactory = (ProtocolProviderFactory) service;
break;
}
}
if (event.getType() == ServiceEvent.REGISTERED)
{
if (logger.isDebugEnabled())
logger.debug("Handling registration of a new Protocol Provider.");
// if we have the PROVIDER_MASK property set, make sure that this
// provider has it and if not ignore it.
String providerMask = System
.getProperty(MetaContactListService.PROVIDER_MASK_PROPERTY);
if (providerMask != null
&& providerMask.trim().length() > 0)
{
String servRefMask = (String) event
.getServiceReference()
.getProperty(
MetaContactListService.PROVIDER_MASK_PROPERTY);
if (servRefMask == null
|| !servRefMask.equals(providerMask))
{
if (logger.isDebugEnabled())
logger.debug("Ignoing masked provider: "
+ provider.getAccountID());
return;
}
}
if(sourceFactory != null
&& currentlyInstalledProviders.containsKey(
provider.getAccountID().getAccountUniqueID()))
{
if (logger.isDebugEnabled())
logger.debug("An already installed account: "
+ provider.getAccountID() + ". Modifying it.");
// the account is already installed and this event is coming
// from a modification. we don't return here as
// the account is removed and added again and we must
// create its unresolved contact and give him a chance to resolve
// them and not fire new subscription to duplicate the already
// existing.
//return;
}
this.handleProviderAdded( (ProtocolProviderService) sService);
}
else if (event.getType() == ServiceEvent.UNREGISTERING)
{
if(sourceFactory == null)
{
//strange ... we must be shutting down. just bail
return;
}
AccountID accountID = provider.getAccountID();
// If the account is still registered or is just unloaded but
// remains stored we remove its contacts but without storing this
if(ContactlistActivator
.getAccountManager()
.getStoredAccounts()
.contains(accountID))
{
//the account is still installed it means we are modifying it.
// we remove all its contacts from current contactlist
// but remove the storage manager in order to avoid
// losing those contacts from the storage
// as its modification later unresolved contacts will be created
// which will be resolved from the already modified account
synchronized(this)
{
this.removeMetaContactListListener(storageManager);
this.handleProviderRemoved(
(ProtocolProviderService)sService);
this.addMetaContactListListener(storageManager);
}
return;
}
if (logger.isDebugEnabled())
logger.debug("Account uninstalled. acc.id="
+provider.getAccountID() +". Removing from meta "
+"contact list.");
this.handleProviderRemoved( (ProtocolProviderService) sService);
}
}
/**
* The class would listen for events delivered to
* <tt>SubscriptionListener</tt>s.
*/
private class ContactListSubscriptionListener
implements SubscriptionListener
{
/**
* Creates a meta contact for the source contact indicated by the
* specified SubscriptionEvent, or updates an existing one if there
* is one. The method would also generate the corresponding
* <tt>MetaContactEvent</tt>.
*
* @param evt the SubscriptionEvent that we'll be handling.
*/
public void subscriptionCreated(SubscriptionEvent evt)
{
if (logger.isTraceEnabled())
logger.trace("Subscription created: " + evt);
//ignore the event if the source contact is in the ignore list
if (isContactInEventIgnoreList(
evt.getSourceContact()
, evt.getSourceProvider()))
{
return;
}
MetaContactGroupImpl parentGroup = (MetaContactGroupImpl)
findMetaContactGroupByContactGroup(evt.getParentGroup());
if (parentGroup == null)
{
logger.error("Received a subscription for a group that we "
+ "hadn't seen before! ");
return;
}
MetaContactImpl newMetaContact = new MetaContactImpl();
newMetaContact.addProtoContact(evt.getSourceContact());
newMetaContact.setDisplayName(evt
.getSourceContact().getDisplayName());
parentGroup.addMetaContact(newMetaContact);
//fire the meta contact event.
fireMetaContactEvent(newMetaContact,
parentGroup,
MetaContactEvent.META_CONTACT_ADDED);
//make sure we have a local copy of the avatar;
newMetaContact.getAvatar();
}
/**
* Indicates that a contact/subscription has been moved from one server
* stored group to another. The way we handle the event depends on
* whether the source contact/subscription is the only proto contact
* found in its current MetaContact encapsulator or not.
* <p>
* If this is the case (the source contact has no siblings in its current
* meta contact list encapsulator) then we will move the whole meta
* contact to the meta contact group corresponding to the new parent
* ContactGroup of the source contact. In this case we would only fire
* a MetaContactMovedEvent containing the old and new parents of the
* MetaContact in question.
* <p>
* If, however, the MetaContact that currently encapsulates the source
* contact also encapsulates other proto contacts, then we will create
* a new MetaContact instance, place it in the MetaContactGroup
* corresponding to the new parent ContactGroup of the source contact
* and add the source contact inside it. In this case we would first
* fire a metacontact added event over the empty meta contact and then,
* once the proto contact has been moved inside it, we would also fire
* a ProtoContactEvent with event id PROTO_CONTACT_MOVED.
* <p>
* @param evt a reference to the SubscriptionMovedEvent containing previous
* and new parents as well as a ref to the source contact.
*/
public void subscriptionMoved(SubscriptionMovedEvent evt)
{
if (logger.isTraceEnabled())
logger.trace("Subscription moved: " + evt);
Contact sourceContact = evt.getSourceContact();
//ignore the event if the source contact is in the ignore list
if (isContactInEventIgnoreList(
sourceContact
, evt.getSourceProvider()))
{
return;
}
MetaContactGroupImpl oldParentGroup = (MetaContactGroupImpl)
findMetaContactGroupByContactGroup(evt.getOldParentGroup());
MetaContactGroupImpl newParentGroup = (MetaContactGroupImpl)
findMetaContactGroupByContactGroup(evt.getNewParentGroup());
if (newParentGroup == null || oldParentGroup == null)
{
logger.error("Received a subscription for a group that we "
+ "hadn't seen before! ");
return;
}
MetaContactImpl currentMetaContact = (MetaContactImpl)
findMetaContactByContact(sourceContact);
if(currentMetaContact == null)
{
logger.warn("Received a move event for a contact that is "
+"not in our contact list."
, new NullPointerException(
"Received a move event for a contact that "
+"is not in our contact list."));
return;
}
//if the move was caused by us (when merging contacts) then chances
//are that the contact is already in the right group
MetaContactGroup currentParentGroup
= currentMetaContact.getParentMetaContactGroup();
if(currentParentGroup == newParentGroup)
{
return;
}
//if the meta contact does not have other children apart from the
//contact that we're currently moving then move the whole meta
//contact to the new parent group.
if( currentMetaContact.getContactCount() == 1 )
{
oldParentGroup.removeMetaContact(currentMetaContact);
newParentGroup.addMetaContact(currentMetaContact);
fireMetaContactEvent(new MetaContactMovedEvent(
currentMetaContact, oldParentGroup, newParentGroup));
}
//if the source contact is not the only contact encapsulated by the
//currentMetaContact, then create a new meta contact in the new
//parent group and move the source contact to it.
else
{
MetaContactImpl newMetaContact = null;
// first check whether a contact hasn't been already added to
// a metacontact
synchronized(sourceContact)
{
//move the proto contact and fire the corresponding event
currentMetaContact.removeProtoContact(sourceContact);
MetaContact checkContact =
findMetaContactByContact(sourceContact);
if(checkContact == null)
{
newMetaContact = new MetaContactImpl();
newMetaContact.setDisplayName(
sourceContact.getDisplayName());
newParentGroup.addMetaContact(newMetaContact);
newMetaContact.addProtoContact(sourceContact);
}
}
// new contact was created
if(newMetaContact != null)
{
//fire an event notifying that a new meta contact was added.
fireMetaContactEvent(newMetaContact,
newParentGroup,
MetaContactEvent.META_CONTACT_ADDED);
fireProtoContactEvent(sourceContact
, ProtoContactEvent.PROTO_CONTACT_MOVED
, currentMetaContact
, newMetaContact);
}
}
}
public void subscriptionFailed(SubscriptionEvent evt)
{
if (logger.isTraceEnabled())
logger.trace("Subscription failed: " + evt);
}
/**
* Events delivered through this method are ignored as they are of no
* interest to this implementation of the meta contact list service.
* @param evt the SubscriptionEvent containing the source contact
*/
public void subscriptionResolved(SubscriptionEvent evt)
{
//this was a contact we already had so all we need to do is
//update it's details
MetaContactImpl mc = (MetaContactImpl) findMetaContactByContact(evt
.getSourceContact());
if(mc != null)
{
mc.getAvatar();
if(mc.getContactCount() == 1
&& !mc.isDisplayNameUserDefined())
{
String oldDisplayName = mc.getDisplayName();
// if we have one contact, display name of metacontact
// haven't been changed by user
// and contact display name is different from
// metacontact's one let's change it
Contact c = mc.getDefaultContact();
String newDisplayName = c.getDisplayName();
if(newDisplayName != null
&& !newDisplayName.equals(oldDisplayName))
{
mc.setDisplayName(newDisplayName);
fireMetaContactEvent(new MetaContactRenamedEvent(
mc, oldDisplayName, newDisplayName));
//changing the display name has surely brought a change in the order as
//well so let's tell the others
fireMetaContactGroupEvent(
findParentMetaContactGroup( mc )
, null
, null
, MetaContactGroupEvent.CHILD_CONTACTS_REORDERED);
}
}
}
}
/**
* In the case where the event refers to a change in the display name
* we compare the old value with the display name of the corresponding
* meta contact. If they are equal this means that the user has not
* specified their own display name for the meta contact and that the
* display name was using this contact's display name for its own
* display name. In this case we change the display name of the meta
* contact to match the new display name of the proto contact.
* <p>
* @param evt the <tt>ContactPropertyChangeEvent</tt> containing the source
* contact and the old and new values of the changed property.
*/
public void contactModified(ContactPropertyChangeEvent evt)
{
MetaContactImpl mc
= (MetaContactImpl)findMetaContactByContact(
evt.getSourceContact());
if( ContactPropertyChangeEvent.PROPERTY_DISPLAY_NAME
.equals(evt.getPropertyName()))
{
if( evt.getOldValue() != null
&& evt.getOldValue().equals(mc.getDisplayName()))
{
renameMetaContact(mc, (String)evt.getNewValue(), false);
}
else
{
//we get here if the name of a contact has changed but the
//meta contact list is not going to reflect any change
//because it is not displaying that name. in this case we
//simply make sure everyone (e.g. the storage manager)
//knows about the change.
fireProtoContactEvent(evt.getSourceContact(),
ProtoContactEvent.PROTO_CONTACT_MODIFIED,
mc,
mc);
}
}
else if( ContactPropertyChangeEvent.PROPERTY_IMAGE
.equals(evt.getPropertyName())
&& evt.getNewValue() != null)
{
changeMetaContactAvatar(
mc,
evt.getSourceContact(),
(byte[])evt.getNewValue());
}
else if(ContactPropertyChangeEvent.PROPERTY_PERSISTENT_DATA
.equals(evt.getPropertyName())
|| ContactPropertyChangeEvent.PROPERTY_DISPLAY_DETAILS
.equals(evt.getPropertyName()))
{
// if persistent data changed fire an event to store it
fireProtoContactEvent(evt.getSourceContact(),
ProtoContactEvent.PROTO_CONTACT_MODIFIED,
mc,
mc);
}
}
/**
* Locates the <tt>MetaContact</tt> corresponding to the contact
* that has been removed and updates it. If the removed proto contact
* was the last one in it, then the <tt>MetaContact</tt> is also
* removed.
*
* @param evt the <tt>SubscriptionEvent</tt> containing the contact
* that has been removed.
*/
public void subscriptionRemoved(SubscriptionEvent evt)
{
if (logger.isTraceEnabled())
logger.trace("Subscription removed: " + evt);
MetaContactImpl metaContact = (MetaContactImpl)
findMetaContactByContact(evt.getSourceContact());
MetaContactGroupImpl metaContactGroup = (MetaContactGroupImpl)
findMetaContactGroupByContactGroup(evt.getParentGroup());
metaContact.removeProtoContact(evt.getSourceContact());
//if this was the last protocol specific contact in this meta
//contact then remove the meta contact as well.
if (metaContact.getContactCount() == 0)
{
metaContactGroup.removeMetaContact(metaContact);
fireMetaContactEvent(metaContact,
metaContactGroup,
MetaContactEvent.META_CONTACT_REMOVED);
}
else
{
//this was not the las proto contact so only generate the
//corresponding event.
fireProtoContactEvent(evt.getSourceContact(),
ProtoContactEvent.PROTO_CONTACT_REMOVED, metaContact, null);
}
}
}
/**
* The class would listen for events delivered to
* <tt>ServerStoredGroupListener</tt>s.
*/
private class ContactListGroupListener
implements ServerStoredGroupListener
{
/**
* The method is called upon receiving notification that a new server
* stored group has been created.
* @param parent a reference to the <tt>MetaContactGroupImpl</tt> where
* <tt>group</tt>'s newly created <tt>MetaContactGroup</tt> wrapper
* should be added as a subgroup.
* @param group the newly added <tt>ContactGroup</tt>
* @return the <tt>MetaContactGroup</tt> that now wraps the newly
* created <tt>ContactGroup</tt>.
*/
private MetaContactGroup handleGroupCreatedEvent(
MetaContactGroupImpl parent,
ContactGroup group)
{
//if parent already contains a meta group with the same name, we'll
//reuse it as the container for the new contact group.
MetaContactGroupImpl newMetaGroup = (MetaContactGroupImpl)parent
.getMetaContactSubgroup(group.getGroupName());
//if there was no meta group with the specified name, create a new
//one
if(newMetaGroup == null)
{
newMetaGroup
= new MetaContactGroupImpl(
MetaContactListServiceImpl.this,
group.getGroupName());
newMetaGroup.addProtoGroup(group);
parent.addSubgroup(newMetaGroup);
}
else
{
newMetaGroup.addProtoGroup(group);
}
//check if there were any subgroups
Iterator<ContactGroup> subgroups = group.subgroups();
while(subgroups.hasNext())
{
ContactGroup subgroup = subgroups.next();
handleGroupCreatedEvent(newMetaGroup, subgroup);
}
Iterator<Contact> contactsIter = group.contacts();
while (contactsIter.hasNext())
{
Contact contact = contactsIter.next();
MetaContactImpl newMetaContact = new MetaContactImpl();
newMetaContact.addProtoContact(contact);
newMetaContact.setDisplayName(contact
.getDisplayName());
newMetaGroup.addMetaContact(newMetaContact);
}
return newMetaGroup;
}
/**
* Adds the source group and its child contacts to the meta contact
* list.
* @param evt the ServerStoredGroupEvent containing the source group.
*/
public void groupCreated(ServerStoredGroupEvent evt)
{
if (logger.isTraceEnabled())
logger.trace("ContactGroup created: " + evt);
//ignore the event if the source group is in the ignore list
if (isGroupInEventIgnoreList(evt.getSourceGroup().getGroupName()
, evt.getSourceProvider()))
{
return;
}
MetaContactGroupImpl parentMetaGroup = (MetaContactGroupImpl)
findMetaContactGroupByContactGroup( evt.getParentGroup());
if (parentMetaGroup == null)
{
logger.error("Failed to identify a parent where group "
+ evt.getSourceGroup().getGroupName() + "should be placed.");
}
// check whether the meta group was already existing before
// adding proto-groups to it
boolean isExisting = parentMetaGroup.getMetaContactSubgroup(
evt.getSourceGroup().getGroupName()) != null;
// add parent group to the ServerStoredGroupEvent
MetaContactGroup newMetaGroup
= handleGroupCreatedEvent(parentMetaGroup, evt.getSourceGroup());
//if this was the first contact group in the meta group fire an
//ADDED event. otherwise fire a modification event.
if(newMetaGroup.countContactGroups() > 1 || isExisting)
{
fireMetaContactGroupEvent(
newMetaGroup
, evt.getSourceProvider()
, evt.getSourceGroup()
, MetaContactGroupEvent.CONTACT_GROUP_ADDED_TO_META_GROUP);
}
else
{
fireMetaContactGroupEvent(
newMetaGroup
, evt.getSourceProvider()
, evt.getSourceGroup()
, MetaContactGroupEvent.META_CONTACT_GROUP_ADDED);
}
}
/**
* Dummy implementation.
* <p>
* @param evt a ServerStoredGroupEvent containing the source group.
*/
public void groupResolved(ServerStoredGroupEvent evt)
{
//we couldn't care less :)
}
/**
* Updates the local contact list by removing the meta contact group
* corresponding to the group indicated by the delivered <tt>evt</tt>
* @param evt the ServerStoredGroupEvent confining the group that has
* been removed.
*/
public void groupRemoved(ServerStoredGroupEvent evt)
{
if (logger.isTraceEnabled())
logger.trace("ContactGroup removed: " + evt);
MetaContactGroupImpl metaContactGroup = (MetaContactGroupImpl)
findMetaContactGroupByContactGroup(evt.getSourceGroup());
if (metaContactGroup == null)
{
logger.error(
"Received a RemovedGroup event for an orphan grp: "
+ evt.getSourceGroup());
return;
}
removeContactGroupFromMetaContactGroup(metaContactGroup,
evt.getSourceGroup(), evt.getSourceProvider());
if(metaContactGroup.countContactGroups() == 0)
{
removeMetaContactGroup(metaContactGroup);
}
}
/**
* Nothing to do here really. Oh yes .... we should actually trigger
* a MetaContactGroup event indicating the change for interested parties
* but that's all.
* @param evt the ServerStoredGroupEvent containing the source group.
*/
public void groupNameChanged(ServerStoredGroupEvent evt)
{
if (logger.isTraceEnabled())
logger.trace("ContactGroup renamed: " + evt);
MetaContactGroup metaContactGroup
= findMetaContactGroupByContactGroup(evt.getSourceGroup());
if(metaContactGroup.countContactGroups() == 1)
{
// if the only group contained in this group is renamed
// rename it
((MetaContactGroupImpl)metaContactGroup)
.setGroupName(evt.getSourceGroup().getGroupName());
}
fireMetaContactGroupEvent(
metaContactGroup
, evt.getSourceProvider()
, evt.getSourceGroup()
, MetaContactGroupEvent.CONTACT_GROUP_RENAMED_IN_META_GROUP);
}
}
/**
* Creates the corresponding MetaContact event and notifies all
* <tt>MetaContactListListener</tt>s that a MetaContact is added or
* removed from the MetaContactList.
*
* @param sourceContact the contact that this event is about.
* @param parentGroup the group that the source contact belongs or belonged
* to.
* @param eventID the id indicating the exavt type of the event to fire.
*/
private synchronized void fireMetaContactEvent(MetaContact sourceContact,
MetaContactGroup parentGroup,
int eventID)
{
MetaContactEvent evt
= new MetaContactEvent(sourceContact, parentGroup, eventID);
if (logger.isTraceEnabled())
logger.trace("Will dispatch the following mcl event: "
+ evt);
for (MetaContactListListener listener : getMetaContactListListeners())
{
switch (evt.getEventID())
{
case MetaContactEvent.META_CONTACT_ADDED:
listener.metaContactAdded(evt);
break;
case MetaContactEvent.META_CONTACT_REMOVED:
listener.metaContactRemoved(evt);
break;
default:
logger.error("Unknown event type " + evt.getEventID());
}
}
}
/**
* Gets a copy of the list of current <code>MetaContactListListener</code>
* interested in events fired by this instance.
*
* @return an array of <code>MetaContactListListener</code>s currently
* interested in events fired by this instance. The returned array
* is a copy of the internal listener storage and thus can be safely
* modified.
*/
private MetaContactListListener[] getMetaContactListListeners()
{
MetaContactListListener[] listeners;
synchronized (metaContactListListeners)
{
listeners
= metaContactListListeners.toArray(
new MetaContactListListener[
metaContactListListeners.size()]);
}
return listeners;
}
/**
* Creates the corresponding <tt>MetaContactPropertyChangeEvent</tt>
* instance and notifies all <tt>MetaContactListListener</tt>s that a
* MetaContact has been modified. Synchronized to avoid firing events
* when we are editing the account (there we temporally remove and then
* add again the storage manager and don't want anybody to interrupt us).
*
* @param event the event to dispatch.
*/
synchronized void fireMetaContactEvent(MetaContactPropertyChangeEvent event)
{
if (logger.isTraceEnabled())
logger.trace("Will dispatch the following mcl property change event: "
+ event);
for (MetaContactListListener listener : getMetaContactListListeners())
{
if (event instanceof MetaContactMovedEvent)
{
listener.metaContactMoved( (MetaContactMovedEvent) event);
}
else if (event instanceof MetaContactRenamedEvent)
{
listener.metaContactRenamed( (MetaContactRenamedEvent) event);
}
else if (event instanceof MetaContactModifiedEvent)
{
listener.metaContactModified( (MetaContactModifiedEvent) event);
}
else if (event instanceof MetaContactAvatarUpdateEvent)
{
listener.metaContactAvatarUpdated(
(MetaContactAvatarUpdateEvent) event);
}
}
}
/**
* Creates the corresponding <tt>ProtoContactEvent</tt> instance and
* notifies all <tt>MetaContactListListener</tt>s that a protocol specific
* <tt>Contact</tt> has been added moved or removed.
* Synchronized to avoid firing events
* when we are editing the account (there we temporally remove and then
* add again the storage manager and don't want anybody to interrupt us).
*
* @param source the contact that has caused the event.
* @param eventName One of the ProtoContactEvent.PROTO_CONTACT_XXX fields
* indicating the exact type of the event.
* @param oldParent the <tt>MetaContact</tt> that was wrapping the source
* <tt>Contact</tt> before the event occurred or <tt>null</tt> if the event
* is caused by adding a new <tt>Contact</tt>
* @param newParent the <tt>MetaContact</tt> that is wrapping the source
* <tt>Contact</tt> after the event occurred or <tt>null</tt> if the event
* is caused by removing a <tt>Contact</tt>
*/
private synchronized void fireProtoContactEvent(Contact source,
String eventName,
MetaContact oldParent,
MetaContact newParent)
{
ProtoContactEvent event
= new ProtoContactEvent(source, eventName, oldParent, newParent );
if (logger.isTraceEnabled())
logger.trace("Will dispatch the following mcl property change event: "
+ event);
for (MetaContactListListener listener : getMetaContactListListeners())
{
if (eventName.equals(ProtoContactEvent.PROTO_CONTACT_ADDED))
{
listener.protoContactAdded(event);
}
else if (eventName.equals(ProtoContactEvent.PROTO_CONTACT_MOVED))
{
listener.protoContactMoved(event);
}
else if (eventName.equals(ProtoContactEvent.PROTO_CONTACT_REMOVED))
{
listener.protoContactRemoved(event);
}
else if (eventName.equals(ProtoContactEvent.PROTO_CONTACT_MODIFIED))
{
listener.protoContactModified(event);
}
}
}
/**
* Upon each status notification this method finds the corresponding meta
* contact and updates the ordering in its parent group.
* <p>
* @param evt the ContactPresenceStatusChangeEvent describing the status
* change.
*/
public void contactPresenceStatusChanged(
ContactPresenceStatusChangeEvent evt)
{
MetaContactImpl metaContactImpl =
(MetaContactImpl) findMetaContactByContact(evt.getSourceContact());
//ignore if we have no meta contact.
if(metaContactImpl == null)
return;
int oldContactIndex = metaContactImpl.getParentGroup()
.indexOf(metaContactImpl);
int newContactIndex = metaContactImpl.reevalContact();
if(oldContactIndex != newContactIndex)
{
fireMetaContactGroupEvent(
findParentMetaContactGroup(metaContactImpl)
, evt.getSourceProvider()
, null
, MetaContactGroupEvent.CHILD_CONTACTS_REORDERED);
}
}
/**
* The method is called from the storage manager whenever a new contact
* group has been parsed and it has to be created.
* @param parentGroup the group that contains the meta contact group we're
* about to load.
* @param metaContactGroupUID the unique identifier of the meta contact
* group.
* @param displayName the name of the meta contact group.
*
* @return the newly created meta contact group.
*/
MetaContactGroupImpl loadStoredMetaContactGroup(
MetaContactGroupImpl parentGroup,
String metaContactGroupUID,
String displayName)
{
//first check if the group exists already.
MetaContactGroupImpl newMetaGroup = (MetaContactGroupImpl) parentGroup
.getMetaContactSubgroupByUID(metaContactGroupUID);
//if the group exists then we have already loaded it for another
//account and we should reuse the same instance.
if(newMetaGroup != null)
return newMetaGroup;
newMetaGroup
= new MetaContactGroupImpl(this, displayName, metaContactGroupUID);
parentGroup.addSubgroup(newMetaGroup);
//I don't think this method needs to produce events since it is
//currently only called upon initialization ... but it doesn't hurt
//trying
fireMetaContactGroupEvent(newMetaGroup, null, null
, MetaContactGroupEvent.META_CONTACT_GROUP_ADDED);
return newMetaGroup;
}
/**
* Creates a unresolved instance of the proto specific contact group
* according to the specified arguments and adds it to
* <tt>containingMetaContactGroup</tt>
*
* @param containingMetaGroup the <tt>MetaContactGroupImpl</tt> where the
* restored contact group should be added.
* @param contactGroupUID the unique identifier of the group.
* @param parentProtoGroup the identifier of the parent proto group.
* @param persistentData the persistent data last returned by the contact
* group.
* @param accountID the ID of the account that the proto group belongs to.
*
* @return a reference to the newly created (unresolved) contact group.
*/
ContactGroup loadStoredContactGroup(MetaContactGroupImpl containingMetaGroup,
String contactGroupUID,
ContactGroup parentProtoGroup,
String persistentData,
String accountID)
{
//get the presence op set
ProtocolProviderService sourceProvider =
currentlyInstalledProviders.get(accountID);
OperationSetPersistentPresence presenceOpSet
= sourceProvider
.getOperationSet(OperationSetPersistentPresence.class);
ContactGroup newProtoGroup = presenceOpSet.createUnresolvedContactGroup(
contactGroupUID, persistentData,
(parentProtoGroup == null)
? presenceOpSet.getServerStoredContactListRoot()
: parentProtoGroup);
containingMetaGroup.addProtoGroup(newProtoGroup);
return newProtoGroup;
}
/**
* The method is called from the storage manager whenever a new contact
* has been parsed and it has to be created.
* @param parentGroup the group that contains the meta contact we're about
* to load.
* @param metaUID the unique identifier of the meta contact.
* @param displayName the display name of the meta contact.
* @param details the details for the contact to create.
* @param protoContacts a list containing descriptors of proto contacts
* encapsulated by the meta contact that we're about to create.
* @param accountID the identifier of the account that the contacts
* originate from.
* @return the loaded meta contact.
*/
MetaContactImpl loadStoredMetaContact(
MetaContactGroupImpl parentGroup,
String metaUID,
String displayName,
Map<String, List<String>> details,
List<MclStorageManager.StoredProtoContactDescriptor> protoContacts,
String accountID)
{
//first check if the meta contact exists already.
MetaContactImpl newMetaContact
= (MetaContactImpl)findMetaContactByMetaUID(metaUID);
if(newMetaContact == null)
{
newMetaContact = new MetaContactImpl(metaUID, details);
newMetaContact.setDisplayName(displayName);
}
//create unresolved contacts for the protocontacts associated with this
//mc
ProtocolProviderService sourceProvider =
currentlyInstalledProviders.get(accountID);
OperationSetPersistentPresence presenceOpSet
= sourceProvider
.getOperationSet(OperationSetPersistentPresence.class);
for (MclStorageManager.StoredProtoContactDescriptor contactDescriptor
: protoContacts)
{
//this contact has already been registered by another meta contact
//so we'll ignore it. If this is the only contact in the meta
//contact, we'll throw an exception at the end of the method and
//cause the mcl storage manager to remove it.
MetaContact mc = findMetaContactByContact(
contactDescriptor.contactAddress, accountID);
if(mc != null)
{
logger.warn("Ignoring duplicate proto contact "
+ contactDescriptor
+ " accountID=" + accountID
+ ". The contact was also present in the "
+ "folloing meta contact:" + mc);
continue;
}
Contact protoContact = presenceOpSet.createUnresolvedContact(
contactDescriptor.contactAddress,
contactDescriptor.persistentData,
( contactDescriptor.parentProtoGroup == null )
? presenceOpSet.getServerStoredContactListRoot()
: contactDescriptor.parentProtoGroup);
newMetaContact.addProtoContact(protoContact);
}
if(newMetaContact.getContactCount() == 0)
{
logger.error("Found an empty meta contact. Throwing an exception "
+ "so that the storage manager would remove it.");
throw new IllegalArgumentException("MetaContact["
+ newMetaContact
+"] contains no non-duplicating child contacts.");
}
parentGroup.addMetaContact(newMetaContact);
fireMetaContactEvent( newMetaContact,
parentGroup,
MetaContactEvent.META_CONTACT_ADDED);
if (logger.isTraceEnabled())
logger.trace("Created meta contact: " + newMetaContact);
return newMetaContact;
}
/**
* Creates the corresponding MetaContactGroup event and notifies all
* <tt>MetaContactListListener</tt>s that a MetaContactGroup is added or
* removed from the MetaContactList.
* Synchronized to avoid firing events
* when we are editing the account (there we temporally remove and then
* add again the storage manager and don't want anybody to interrupt us).
*
* @param source
* the MetaContactGroup instance that is added to the
* MetaContactList
* @param provider
* the ProtocolProviderService instance where this event occurred
* @param sourceProtoGroup the proto group associated with this event or
* null if the event does not concern a particular source group.
* @param eventID
* one of the METACONTACT_GROUP_XXX static fields indicating the
* nature of the event.
*/
private synchronized void fireMetaContactGroupEvent( MetaContactGroup source,
ProtocolProviderService provider,
ContactGroup sourceProtoGroup,
int eventID)
{
MetaContactGroupEvent evt = new MetaContactGroupEvent(
source, provider, sourceProtoGroup, eventID);
if (logger.isTraceEnabled())
logger.trace("Will dispatch the following mcl event: "
+ evt);
for (MetaContactListListener listener : getMetaContactListListeners())
{
switch (eventID)
{
case MetaContactGroupEvent.META_CONTACT_GROUP_ADDED:
listener.metaContactGroupAdded(evt);
break;
case MetaContactGroupEvent.META_CONTACT_GROUP_REMOVED:
listener.metaContactGroupRemoved(evt);
break;
case MetaContactGroupEvent.CHILD_CONTACTS_REORDERED:
listener.childContactsReordered(evt);
break;
case MetaContactGroupEvent
.META_CONTACT_GROUP_RENAMED:
case MetaContactGroupEvent
.CONTACT_GROUP_RENAMED_IN_META_GROUP:
case MetaContactGroupEvent
.CONTACT_GROUP_REMOVED_FROM_META_GROUP:
case MetaContactGroupEvent
.CONTACT_GROUP_ADDED_TO_META_GROUP:
listener.metaContactGroupModified(evt);
break;
default:
logger.error("Unknown event type (" + eventID
+ ") for event: " + evt);
}
}
}
/**
* Utility class used for blocking the current thread until an event
* is delivered confirming the creation of a particular group.
*/
private static class BlockingGroupEventRetriever
implements ServerStoredGroupListener
{
private final String groupName;
public ServerStoredGroupEvent evt = null;
/**
* Creates an instance of the retriever that will wait for events
* confirming the creation of the group with the specified name.
* @param groupName the name of the group whose birth we're waiting for.
*/
BlockingGroupEventRetriever(String groupName)
{
this.groupName = groupName;
}
/**
* Called whoever an indication is received that a new server stored
* group is created.
* @param event a ServerStoredGroupChangeEvent containing a reference to
* the newly created group.
*/
public synchronized void groupCreated(ServerStoredGroupEvent event)
{
if (event.getSourceGroup().getGroupName().equals(groupName))
{
this.evt = event;
this.notifyAll();
}
}
/**
* Evens delivered through this method are ignored
* @param event param ignored
*/
public void groupRemoved(ServerStoredGroupEvent event)
{}
/**
* Evens delivered through this method are ignored
* @param event param ignored
*/
public void groupNameChanged(ServerStoredGroupEvent event)
{}
/**
* Evens delivered through this method are ignored
* @param event param ignored
*/
public void groupResolved(ServerStoredGroupEvent event)
{}
/**
* Block the execution of the current thread until either a group
* created event is received or milis miliseconds pass.
* @param millis the number of millis that we should wait before we
* determine failure.
*/
public synchronized void waitForEvent(long millis)
{
//no need to wait if an event is already there.
if (evt == null)
{
try
{
this.wait(millis);
}
catch (InterruptedException ex)
{
logger.error("Interrupted while waiting for group creation",
ex);
}
}
}
}
/**
* Utility class used for blocking the current thread until an event
* is delivered confirming the creation of a particular contact.
*/
private static class BlockingSubscriptionEventRetriever
implements SubscriptionListener,
ServerStoredGroupListener
{
private final String subscriptionAddress;
public Contact sourceContact = null;
public EventObject evt = null;
/**
* Events delivered through this method are ignored
* @param event param ignored
*/
public void groupResolved(ServerStoredGroupEvent event)
{}
/**
* Events delivered through this method are ignored
* @param event param ignored
*/
public void groupRemoved(ServerStoredGroupEvent event)
{}
/**
* Events delivered through this method are ignored
* @param event param ignored
*/
public void groupNameChanged(ServerStoredGroupEvent event)
{}
/**
* Creates an instance of the retriever that will wait for events
* confirming the creation of the subscription with the specified
* address.
* @param subscriptionAddress the name of the group whose birth we're waiting for.
*/
BlockingSubscriptionEventRetriever(String subscriptionAddress)
{
this.subscriptionAddress = subscriptionAddress;
}
/**
* Called whenever an indication is received that a new server stored group
* is created.
* @param event a ServerStoredGroupEvent containing a reference to the
* newly created group.
*/
public synchronized void groupCreated(ServerStoredGroupEvent event)
{
Contact contact
= event.getSourceGroup().getContact(subscriptionAddress);
if ( contact != null)
{
this.evt = event;
this.sourceContact = contact;
this.notifyAll();
}
}
/**
* Called whenever an indication is received that a subscription is
* created.
* @param event a <tt>SubscriptionEvent</tt> containing a reference to
* the newly created contact.
*/
public synchronized void subscriptionCreated(SubscriptionEvent event)
{
if (event.getSourceContact().getAddress()
.equals(subscriptionAddress)
|| event.getSourceContact().equals(subscriptionAddress))
{
this.evt = event;
this.sourceContact = event.getSourceContact();
this.notifyAll();
}
}
/**
* Events delivered through this method are ignored
* @param event param ignored
*/
public void subscriptionRemoved(SubscriptionEvent event)
{}
/**
* Called whenever an indication is received that a subscription
* creation has failed.
* @param event a <tt>SubscriptionEvent</tt> containing a reference to
* the contact we are trying to subscribe.
*/
public synchronized void subscriptionFailed(SubscriptionEvent event)
{
if (event.getSourceContact().getAddress()
.equals(subscriptionAddress))
{
this.evt = event;
this.sourceContact = event.getSourceContact();
this.notifyAll();
}
}
/**
* Events delivered through this method are ignored
* @param event param ignored
*/
public void subscriptionMoved(SubscriptionMovedEvent event)
{}
/**
* Events delivered through this method are ignored
* @param event param ignored
*/
public void subscriptionResolved(SubscriptionEvent event)
{}
/**
* Events delivered through this method are ignored
* @param event param ignored
*/
public void contactModified(ContactPropertyChangeEvent event)
{}
/**
* Block the execution of the current thread until either a contact
* created event is received or milis miliseconds pass.
* @param millis the number of milis to wait upon determining a failure.
*/
public synchronized void waitForEvent(long millis)
{
//no need to wait if an event is already there.
if (evt == null)
{
try
{
this.wait(millis);
}
catch (InterruptedException ex)
{
logger.error(
"Interrupted while waiting for contact creation"
, ex);
}
}
}
}
/**
* Notifies this listener that the list of the <tt>OperationSet</tt>
* capabilities of a <tt>Contact</tt> has changed.
*
* @param event a <tt>ContactCapabilitiesEvent</tt> with ID
* {@link ContactCapabilitiesEvent#SUPPORTED_OPERATION_SETS_CHANGED} which
* specifies the <tt>Contact</tt> whose list of <tt>OperationSet</tt>
* capabilities has changed
*/
public void supportedOperationSetsChanged(ContactCapabilitiesEvent event)
{
// If the source contact isn't contained in this meta contact we have
// nothing more to do here.
MetaContactImpl metaContactImpl
= (MetaContactImpl) findMetaContactByContact(
event.getSourceContact());
//ignore if we have no meta contact.
if(metaContactImpl == null)
return;
Contact contact = event.getSourceContact();
metaContactImpl.updateCapabilities(contact, event.getOperationSets());
fireProtoContactEvent( contact,
ProtoContactEvent.PROTO_CONTACT_MODIFIED,
metaContactImpl,
metaContactImpl);
}
}