/*
* 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.io.*;
import java.util.*;
import javax.xml.parsers.*;
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.util.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.fileaccess.*;
import org.jitsi.util.xml.*;
import org.jitsi.util.xml.XMLUtils;
import org.osgi.framework.*;
import org.w3c.dom.*;
/**
* The class handles read / write operations over the file where a persistent
* copy of the meta contact list is stored.
* <p>
* The load / resolve strategy that we use when storing contact lists is roughly
* the following:
* <p>
* 1) The MetaContactListService is started. <br>
* 2) If no file exists for the meta contact list, create one. <br>
* 3) We receive an OSGI event telling us that a new ProtocolProviderService is
* registered or we simply retrieve one that was already in the bundle <br>
* 4) We look through the contact list file and load groups and contacts
* belonging to this new provider. Unresolved proto groups and contacts will be
* created for every one of them.
* <p>
*
* @author Emil Ivov
*/
public class MclStorageManager
implements MetaContactListListener
{
/**
* Our logger.
*/
private static final Logger logger
= Logger.getLogger(MclStorageManager.class);
/**
* The property to enable multi tenant mode. When changing profiles/accounts
* the contactlist can be filled with groups and contacts from protocol
* provider we do not know about. This mode will prevent loading empty
* and groups we do not know about.
*/
private static final String MULTI_TENANT_MODE_PROP =
"net.java.sip.communicator.impl.contactlist.MULTI_TENANT_MODE";
/**
* Whether MULTI_TENANT_MODE_PROP has been enabled.
*/
private boolean multiTenantMode = false;
/**
* Indicates whether the storage manager has been properly started or in
* other words that it has successfully found and read the xml contact list
* file.
*/
private boolean started = false;
/**
* Indicates whether there has been a change since the last time we stored
* this contact list. Used by the storage methods.
*/
private boolean isModified = false;
/**
* A currently valid reference to the OSGI bundle context,
*/
private BundleContext bundleContext = null;
/**
* The file access service that we'll be using in order to obtain a
* reference to the contact list file.
*/
private FileAccessService faService = null;
/**
* The name of the system property that stores the name of the contact list
* file.
*/
private static final String FILE_NAME_PROPERTY =
"net.java.sip.communicator.CONTACTLIST_FILE_NAME";
/**
* The XML Document containing the contact list file.
*/
private Document contactListDocument = null;
/**
* A reference to the file containing the locally stored meta contact list.
*/
private File contactlistFile = null;
/**
* A reference to the failsafe transaction used with the contactlist file.
*/
private FailSafeTransaction contactlistTrans = null;
/**
* A reference to the MetaContactListServiceImpl that created and started
* us.
*/
private MetaContactListServiceImpl mclServiceImpl = null;
/**
* The name of the system property that stores the name of the contact list
* file.
*/
private static final String DEFAULT_FILE_NAME = "contactlist.xml";
/**
* The name of the node that represents the contact list root.
*/
private static String DOCUMENT_ROOT_NAME = "sip-communicator";
/**
* The name of the XML node corresponding to a meta contact group.
*/
private static final String GROUP_NODE_NAME = "group";
/**
* The name of the XML node corresponding to a collection of meta contact
* subgroups.
*/
private static final String SUBGROUPS_NODE_NAME = "subgroups";
/**
* The name of the XML attribute that contains group names.
*/
private static final String GROUP_NAME_ATTR_NAME = "name";
/**
* The name of the XML attribute that contains group UIDs.
*/
private static final String GROUP_UID_ATTR_NAME = "uid";
/**
* The name of the XML node that contains protocol specific group
* descriptorS.
*/
private static final String PROTO_GROUPS_NODE_NAME = "proto-groups";
/**
* The name of the XML node that contains A protocol specific group
* descriptor.
*/
private static final String PROTO_GROUP_NODE_NAME = "proto-group";
/**
* The name of the XML attribute that contains unique identifiers
*/
private static final String UID_ATTR_NAME = "uid";
/**
* The name of the XML attribute that contains unique identifiers for parent
* contact groups.
*/
private static final String PARENT_PROTO_GROUP_UID_ATTR_NAME =
"parent-proto-group-uid";
/**
* The name of the XML attribute that contains account identifiers
* indicating proto group's and proto contacts' owning providers.
*/
private static final String ACCOUNT_ID_ATTR_NAME = "account-id";
/**
* The name of the XML node that contains meta contact details.
*/
private static final String META_CONTACT_NODE_NAME = "meta-contact";
/**
* The name of the XML node that contains meta contact display names.
*/
private static final String META_CONTACT_DISPLAY_NAME_NODE_NAME =
"display-name";
/**
* The name of the XML attribute that contains true/false, whether
* this meta contact was renamed by user.
*/
private static final String USER_DEFINED_DISPLAY_NAME_ATTR_NAME =
"user-defined";
/**
* The name of the XML node that contains meta contact detail.
*/
private static final String META_CONTACT_DETAIL_NAME_NODE_NAME = "detail";
/**
* The name of the XML attribute that contains detail name.
*/
private static final String DETAIL_NAME_ATTR_NAME = "name";
/**
* The name of the XML attribute that contains detail value.
*/
private static final String DETAIL_VALUE_ATTR_NAME = "value";
/**
* The name of the XML node that contains information of a proto contact
*/
private static final String PROTO_CONTACT_NODE_NAME = "contact";
/**
* The name of the XML node that contains information of a proto contact
*/
private static final String PROTO_CONTACT_ADDRESS_ATTR_NAME = "address";
/**
* The name of the XML node that contains information that contacts or
* groups returned as persistent and that should be used when restoring a
* contact or a group.
*/
private static final String PERSISTENT_DATA_NODE_NAME = "persistent-data";
/**
* The name of the XML node that contains all meta contact nodes inside a
* group
*/
private static final String CHILD_CONTACTS_NODE_NAME = "child-contacts";
/**
* A lock that we use when storing the contact list to avoid being exited
* while in there.
*/
private static final Object contactListRWLock = new Object();
/**
* Determines whether the storage manager has been properly started or in
* other words that it has successfully found and read the xml contact list
* file.
*
* @return true if the storage manager has been successfully initialized and
* false otherwise.
*/
boolean isStarted()
{
return started;
}
/**
* Prepares the storage manager for shutdown.
*/
public void stop()
{
if (logger.isTraceEnabled())
logger.trace("Stopping the MCL XML storage manager.");
this.started = false;
synchronized (contactListRWLock)
{
contactListRWLock.notifyAll();
}
}
/**
* Initializes the storage manager and makes it do initial load and parsing
* of the contact list file.
*
* @param bc a reference to the currently valid OSGI <tt>BundleContext</tt>
* @param mclServImpl a reference to the currently valid instance of the
* <tt>MetaContactListServiceImpl</tt> that we could use to pass
* parsed contacts and contact groups.
* @throws IOException if the contact list file specified file does not
* exist and could not be created.
* @throws XMLException if there is a problem with the file syntax.
*/
void start(BundleContext bc, MetaContactListServiceImpl mclServImpl)
throws IOException,
XMLException
{
bundleContext = bc;
// retrieve a reference to the file access service.
faService
= ServiceUtils.getService(bundleContext, FileAccessService.class);
// retrieve a reference to the file access service.
ConfigurationService configurationService
= ServiceUtils.getService(
bundleContext,
ConfigurationService.class);
String fileName = configurationService.getString(FILE_NAME_PROPERTY);
if (fileName == null)
{
fileName = System.getProperty(FILE_NAME_PROPERTY);
if (fileName == null)
fileName = DEFAULT_FILE_NAME;
}
// get a reference to the contact list file.
try
{
contactlistFile
= faService.getPrivatePersistentFile(
fileName,
FileCategory.PROFILE);
if (!contactlistFile.exists() && !contactlistFile.createNewFile())
{
throw new IOException(
"Failed to create file"
+ contactlistFile.getAbsolutePath());
}
}
catch (Exception ex)
{
logger.error("Failed to get a reference to the contact list file.",
ex);
throw new IOException("Failed to get a reference to the contact "
+ "list file=" + fileName + ". error was:" + ex.getMessage());
}
multiTenantMode = configurationService.getBoolean(
MULTI_TENANT_MODE_PROP, multiTenantMode);
// create the failsafe transaction and restore the file if needed
try
{
contactlistTrans =
faService.createFailSafeTransaction(this.contactlistFile);
contactlistTrans.restoreFile();
}
catch (NullPointerException e)
{
logger.error("the contactlist file is null", e);
}
catch (IllegalStateException e)
{
logger.error("The contactlist file can't be found", e);
}
try
{
// load the contact list
DocumentBuilder builder
= XMLUtils.newDocumentBuilderFactory().newDocumentBuilder();
if (contactlistFile.length() == 0)
{
// if the contact list does not exist - create it.
contactListDocument = builder.newDocument();
initVirginDocument(mclServImpl, contactListDocument);
// write the contact list so that it is there for the parser
storeContactList0();
}
else
{
try
{
contactListDocument = builder.parse(contactlistFile);
}
catch (Throwable ex)
{
logger.error("Error parsing configuration file", ex);
logger.error("Creating replacement file");
// re-create and re-init the new document
contactlistFile.delete();
contactlistFile.createNewFile();
contactListDocument = builder.newDocument();
initVirginDocument(mclServImpl, contactListDocument);
// write the contact list so that it is there for the parser
storeContactList0();
}
}
}
catch (ParserConfigurationException ex)
{
// it is not highly probable that this might happen - so lets just
// log it.
logger.error("Error finding configuration for default parsers", ex);
}
mclServImpl.addMetaContactListListener(this);
this.mclServiceImpl = mclServImpl;
started = true;
this.launchStorageThread();
}
/**
* Stores the contact list in its current state.
*
* @throws IOException if writing fails.
*/
private void scheduleContactListStorage() throws IOException
{
synchronized (contactListRWLock)
{
if (!isStarted())
return;
this.isModified = true;
contactListRWLock.notifyAll();
}
}
/**
* Writes the contact list on the hard disk.
*
* @throws IOException in case writing fails.
*/
private void storeContactList0() throws IOException
{
if (logger.isTraceEnabled())
logger.trace("storing contact list. because is started =="
+ isStarted());
if (logger.isTraceEnabled())
logger.trace("storing contact list. because is modified =="
+ isModified);
if (isStarted())
{
// begin a new transaction
try
{
contactlistTrans.beginTransaction();
}
catch (IllegalStateException e)
{
logger.error("the contactlist file is missing", e);
}
// really write the modification
OutputStream stream = new FileOutputStream(contactlistFile);
XMLUtils.indentedWriteXML(contactListDocument, stream);
stream.close();
// commit the changes
try
{
contactlistTrans.commit();
}
catch (IllegalStateException e)
{
logger.error("the contactlist file is missing", e);
}
}
}
/**
* Launches a separate thread that waits on the contact list rw lock and
* when notified stores the contact list in case there have been
* modifications since last time it saved.
*/
private void launchStorageThread()
{
new Thread()
{
@Override
public void run()
{
try
{
synchronized (contactListRWLock)
{
while (isStarted())
{
contactListRWLock.wait(5000);
if (isModified)
{
storeContactList0();
isModified = false;
}
}
}
}
catch (IOException ex)
{
logger.error("Storing contact list failed", ex);
started = false;
}
catch (InterruptedException ex)
{
logger.error("Storing contact list failed", ex);
started = false;
}
}
}.start();
}
/**
* Stops the storage manager and performs a final write
*/
public void storeContactListAndStopStorageManager()
{
synchronized (contactListRWLock)
{
if (!isStarted())
return;
started = false;
// make sure everyone gets released after we finish.
contactListRWLock.notifyAll();
// write the contact list ourselves before we go out..
try
{
storeContactList0();
}
catch (IOException ex)
{
logger
.debug("Failed to store contact list before stopping", ex);
}
}
}
/**
* update persistent data in the dom object model for the given metacontact
* and its contacts
*
* @param metaContact MetaContact target meta contact
*/
private void updatePersistentDataForMetaContact(MetaContact metaContact)
{
Element metaContactNode = findMetaContactNode(metaContact.getMetaUID());
Iterator<Contact> iter = metaContact.getContacts();
while (iter.hasNext())
{
Contact item = iter.next();
String persistentData = item.getPersistentData();
/*
* TODO If persistentData is null and persistentDataNode exists and
* contains non-null text, will persistentDataNode be left without
* updating?
*/
if (persistentData != null)
{
Element currentNode =
XMLUtils.locateElement(metaContactNode,
PROTO_CONTACT_NODE_NAME,
PROTO_CONTACT_ADDRESS_ATTR_NAME, item.getAddress());
Element persistentDataNode =
XMLUtils.findChild(currentNode, PERSISTENT_DATA_NODE_NAME);
// if node does not exist - create it
if (persistentDataNode == null)
{
persistentDataNode =
contactListDocument
.createElement(PERSISTENT_DATA_NODE_NAME);
currentNode.appendChild(persistentDataNode);
}
XMLUtils.setText(persistentDataNode, persistentData);
}
}
}
/**
* Fills the document with the tags necessary for it to be filled properly
* as the meta contact list evolves.
*
* @param mclServImpl the meta contact list service to use when
* initializing the document.
* @param contactListDoc the document to init.
*/
private void initVirginDocument(MetaContactListServiceImpl mclServImpl,
Document contactListDoc)
{
Element root = contactListDoc.createElement(DOCUMENT_ROOT_NAME);
contactListDoc.appendChild(root);
// create the rootGroup
Element rootGroup =
createMetaContactGroupNode(mclServImpl.getRoot());
root.appendChild(rootGroup);
}
/**
* Parses the contact list file and calls corresponding "add" methods
* belonging to <tt>mclServiceImpl</tt> for every meta contact and meta
* contact group stored in the (contactlist.xml) file that correspond to a
* provider caring the specified <tt>accountID</tt>.
*
* @param accountID the identifier of the account whose contacts we're
* interested in.
* @throws XMLException if a problem occurs while parsing contact list
* contents.
*/
void extractContactsForAccount(String accountID) throws XMLException
{
if (!isStarted())
return;
// we don't want to receive meta contact events triggered by ourselves
// so we stop listening. it is possible but very unlikely that other
// events, not triggered by us are received while we're off the channel
// but that would be a very bizzare case ..... I guess we got to live
// with the risk.
this.mclServiceImpl.removeMetaContactListListener(this);
try
{
Element root =
findMetaContactGroupNode(mclServiceImpl.getRoot().getMetaUID());
if (root == null)
{
// If there is no root, there is definitely something wrong
// really broken file will create it again
logger
.fatal("The contactlist file is recreated cause its broken");
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
contactListDocument = builder.newDocument();
initVirginDocument(mclServiceImpl, contactListDocument);
// write the contact list so that it is there for the parser
storeContactList0();
}
else
{
// if there is root lets parse it
// parse the group node and extract all its child groups and
// contacts
processGroupXmlNode(mclServiceImpl, accountID, root, null, null);
// now save the contact list in case it has changed
scheduleContactListStorage();
}
}
catch (Throwable exc)
{
// catch everything because we MUST NOT disturb the thread
// initializing the meta CL for a new provider with null point
// exceptions or others of the sort
throw new XMLException("Failed to extract contacts for account "
+ accountID, exc);
}
finally
{
// now that we're done updating the contact list we can start
// listening
// again
this.mclServiceImpl.addMetaContactListListener(this);
}
}
/**
* Parses <tt>groupNode</tt> and all of its subnodes, creating corresponding
* instances through <tt>mclServiceImpl</tt> as children of
* <tt>parentGroup</tt>
*
* @param mclServImpl the <tt>MetaContactListServiceImpl</tt> for
* creating new contacts and groups.
* @param accountID a String identifier of the account whose contacts we're
* interested in.
* @param groupNode the XML <tt>Element</tt> that points to the group we're
* currently parsing.
* @param parentGroup the <tt>MetaContactGroupImpl</tt> where we should be
* creating children.
* @param parentProtoGroups a Map containing all proto groups that could be
* parents of any groups parsed from the specified groupNode. The
* map binds UIDs to group references and may be null for top
* level groups.
*/
private void processGroupXmlNode(MetaContactListServiceImpl mclServImpl,
String accountID, Element groupNode, MetaContactGroupImpl parentGroup,
Map<String, ContactGroup> parentProtoGroups)
{
// first resolve the group itself.(unless this is the meta contact list
// root which is already resolved)
MetaContactGroupImpl currentMetaGroup = null;
// in this map we store all proto groups that we find in this meta group
// (unless this is the MCL root)in order to pass them as parent
// references to any subgroups.
Map<String, ContactGroup> protoGroupsMap =
new Hashtable<String, ContactGroup>();
if (parentGroup == null)
{
currentMetaGroup = mclServImpl.rootMetaGroup;
}
else
{
String groupMetaUID =
XMLUtils.getAttribute(groupNode, GROUP_UID_ATTR_NAME);
String groupDisplayName =
XMLUtils.getAttribute(groupNode, GROUP_NAME_ATTR_NAME);
// create the meta group
if(!multiTenantMode)
{
currentMetaGroup =
mclServImpl.loadStoredMetaContactGroup(parentGroup,
groupMetaUID, groupDisplayName);
}
// extract and load one by one all proto groups in this meta group.
Node protoGroupsNode =
XMLUtils.findChild(groupNode, PROTO_GROUPS_NODE_NAME);
NodeList protoGroups = protoGroupsNode.getChildNodes();
for (int i = 0; i < protoGroups.getLength(); i++)
{
Node currentProtoGroupNode = protoGroups.item(i);
if (currentProtoGroupNode.getNodeType() != Node.ELEMENT_NODE)
continue;
String groupAccountID =
XMLUtils.getAttribute(currentProtoGroupNode,
ACCOUNT_ID_ATTR_NAME);
if (!accountID.equals(groupAccountID))
continue;
String protoGroupUID =
XMLUtils.getAttribute(currentProtoGroupNode, UID_ATTR_NAME);
String parentProtoGroupUID =
XMLUtils.getAttribute(currentProtoGroupNode,
PARENT_PROTO_GROUP_UID_ATTR_NAME);
Element persistentDataNode =
XMLUtils.findChild((Element) currentProtoGroupNode,
PERSISTENT_DATA_NODE_NAME);
String persistentData = "";
if (persistentDataNode != null)
{
persistentData = XMLUtils.getText(persistentDataNode);
}
// try to find the parent proto group for the one we're
// currently
// parsing.
ContactGroup parentProtoGroup = null;
if (parentProtoGroups != null && parentProtoGroups.size() > 0)
parentProtoGroup =
parentProtoGroups.get(parentProtoGroupUID);
// create the meta group if it is not already created
if(multiTenantMode && currentMetaGroup == null)
{
// only create metacontact group if we have a matching
// proto group, skips creating empty groups, or for non
// existing providers
currentMetaGroup =
mclServImpl.loadStoredMetaContactGroup(parentGroup,
groupMetaUID, groupDisplayName);
}
// create the proto group
ContactGroup newProtoGroup =
mclServImpl.loadStoredContactGroup(currentMetaGroup,
protoGroupUID, parentProtoGroup, persistentData,
accountID);
protoGroupsMap.put(protoGroupUID, newProtoGroup);
}
// if this is not the meta contact list root and if it doesn't
// contain proto groups of the account we're currently loading then
// we don't need to recurese it since it cound contain any child
// contacts of the same account.
if (protoGroupsMap.size() == 0)
return;
}
// we have parsed groups now go over the children
Node childContactsNode =
XMLUtils.findChild(groupNode, CHILD_CONTACTS_NODE_NAME);
NodeList childContacts =
(childContactsNode == null) ? null : childContactsNode
.getChildNodes();
// go over every meta contact, extract its details and its encapsulated
// proto contacts
for (int i = 0; childContacts != null && i < childContacts.getLength(); i++)
{
Node currentMetaContactNode = childContacts.item(i);
if (currentMetaContactNode.getNodeType() != Node.ELEMENT_NODE)
continue;
try
{
String uid =
XMLUtils
.getAttribute(currentMetaContactNode, UID_ATTR_NAME);
Element displayNameNode =
XMLUtils.findChild((Element) currentMetaContactNode,
META_CONTACT_DISPLAY_NAME_NODE_NAME);
String displayName = XMLUtils.getText(displayNameNode);
boolean isDisplayNameUserDefined =
Boolean.valueOf(displayNameNode
.getAttribute(USER_DEFINED_DISPLAY_NAME_ATTR_NAME));
// extract a map of all encapsulated proto contacts
List<MclStorageManager.StoredProtoContactDescriptor> protoContacts =
extractProtoContacts((Element) currentMetaContactNode,
accountID, protoGroupsMap);
// if the size of the map is 0 then the meta contact does not
// contain any contacts matching the currently parsed account
// id.
if (protoContacts.size() < 1)
continue;
// Extract contact details.
Map<String, List<String>> details = null;
try
{
List<Element> detailsNodes =
XMLUtils.findChildren((Element) currentMetaContactNode,
META_CONTACT_DETAIL_NAME_NODE_NAME);
if (detailsNodes.size() > 0)
{
details = new Hashtable<String, List<String>>();
for (Element e : detailsNodes)
{
String name = e.getAttribute(DETAIL_NAME_ATTR_NAME);
String value
= e.getAttribute(DETAIL_VALUE_ATTR_NAME);
List<String> detailsObj = details.get(name);
if (detailsObj == null)
{
List<String> ds = new ArrayList<String>();
ds.add(value);
details.put(name, ds);
}
else
detailsObj.add(value);
}
}
}
catch (Exception ex)
{
// catch any exception from loading contacts
// that will prevent loading the contact
logger.error("Cannot load details for contact node "
+ currentMetaContactNode, ex);
}
// pass the parsed proto contacts to the mcl service
MetaContactImpl mc = mclServImpl.loadStoredMetaContact(
currentMetaGroup, uid,
displayName, details, protoContacts, accountID);
if(isDisplayNameUserDefined)
mc.setDisplayNameUserDefined(true);
}
catch (Throwable thr)
{
// if we fail parsing a meta contact, we should remove it so
// that
// it stops causing trouble, and let other meta contacts load.
logger.warn("Failed to parse meta contact "
+ currentMetaContactNode
+ ". Will remove and continue with other contacts", thr);
// remove the node so that it doesn't cause us problems again
if (currentMetaContactNode.getParentNode() != null)
{
try
{
currentMetaContactNode.getParentNode().removeChild(
currentMetaContactNode);
}
catch (Throwable throwable)
{
// hmm, failed to remove the faulty node. we must be
// in some kind of serious troble (but i don't see
// what we can do about it)
logger.error("Failed to remove meta contact node "
+ currentMetaContactNode, throwable);
}
}
}
}
// now, last thing that's left to do - go over all subgroups if any
Node subgroupsNode = XMLUtils.findChild(groupNode, SUBGROUPS_NODE_NAME);
if (subgroupsNode == null)
return;
NodeList subgroups = subgroupsNode.getChildNodes();
// recurse for every sub meta group
for (int i = 0; i < subgroups.getLength(); i++)
{
Node currentGroupNode = subgroups.item(i);
if (currentGroupNode.getNodeType() != Node.ELEMENT_NODE
|| !currentGroupNode.getNodeName().equals(GROUP_NODE_NAME))
continue;
try
{
processGroupXmlNode(mclServImpl, accountID,
(Element) currentGroupNode, currentMetaGroup,
protoGroupsMap);
}
catch (Throwable throwable)
{
// catch everything and bravely continue with remaining groups
// and contacts
logger.error("Failed to process group node " + currentGroupNode
+ ". Removing.", throwable);
// remove the node so that it doesn't cause us problems again
if (currentGroupNode.getParentNode() != null)
{
try
{
currentGroupNode.getParentNode().removeChild(
currentGroupNode);
}
catch (Throwable thr)
{
// hmm, failed to remove the faulty node. we must be
// in some kind of serious troble (but i don't see
// what we can do about it)
logger.error("Failed to remove group node "
+ currentGroupNode, thr);
}
}
}
}
}
/**
* Returns all proto contacts that are encapsulated inside the meta contact
* represented by <tt>metaContactNode</tt> and that originate from the
* account with id - <tt>accountID</tt>. The returned list contains contact
* contact descriptors as elements. In case the meta contact does not
* contain proto contacts originating from the specified account, an empty
* list is returned.
* <p>
*
* @param metaContactNode the Element whose proto contacts we'd like to
* extract.
* @param accountID the id of the account whose contacts we're interested
* in.
* @param protoGroups a map binding proto group UIDs to protogroups, that
* the method could use i n order to fill in the corresponding
* field in the contact descriptors.
* @return a java.util.List containing contact descriptors.
*/
private List<MclStorageManager.StoredProtoContactDescriptor>
extractProtoContacts(Element metaContactNode,
String accountID,
Map<String, ContactGroup> protoGroups)
{
if(logger.isTraceEnabled())
logger.trace("Extracting proto contacts for "
+ XMLUtils.getAttribute( metaContactNode, "uid"));
List<StoredProtoContactDescriptor> protoContacts =
new LinkedList<StoredProtoContactDescriptor>();
NodeList children = metaContactNode.getChildNodes();
List<Node> duplicates = new LinkedList<Node>();
for (int i = 0; i < children.getLength(); i++)
{
Node currentNode = children.item(i);
//for some reason every now and then we would get a null nodename
//... go figure
if (currentNode.getNodeName() == null)
continue;
if (currentNode.getNodeType() != Node.ELEMENT_NODE
|| !currentNode.getNodeName().equals(PROTO_CONTACT_NODE_NAME))
continue;
String contactAccountID =
XMLUtils.getAttribute(currentNode, ACCOUNT_ID_ATTR_NAME);
if (!accountID.equals(contactAccountID))
continue;
String contactAddress =
XMLUtils.getAttribute(currentNode,
PROTO_CONTACT_ADDRESS_ATTR_NAME);
if (StoredProtoContactDescriptor.findContactInList(contactAddress,
protoContacts) != null)
{
// this is a duplicate. mark for removal and continue
duplicates.add(currentNode);
continue;
}
String protoGroupUID =
XMLUtils.getAttribute(currentNode,
PARENT_PROTO_GROUP_UID_ATTR_NAME);
Element persistentDataNode =
XMLUtils.findChild((Element) currentNode,
PERSISTENT_DATA_NODE_NAME);
String persistentData =
(persistentDataNode == null) ? "" : XMLUtils
.getText(persistentDataNode);
protoContacts.add(new StoredProtoContactDescriptor(contactAddress,
persistentData, protoGroups.get(protoGroupUID)));
}
// remove all duplicates
for (Node node : duplicates)
{
metaContactNode.removeChild(node);
}
return protoContacts;
}
/**
* Creates a node element corresponding to <tt>protoContact</tt>. If
* required data is missing returns null.
*
* @param protoContact the Contact whose element we'd like to create
* @return a XML Element corresponding to <tt>protoContact</tt>
* or <tt>null</tt> if required data is not present.
*/
private Element createProtoContactNode(Contact protoContact)
{
Element protoContactElement =
contactListDocument.createElement(PROTO_CONTACT_NODE_NAME);
// set attributes
protoContactElement.setAttribute(PROTO_CONTACT_ADDRESS_ATTR_NAME,
protoContact.getAddress());
protoContactElement.setAttribute(ACCOUNT_ID_ATTR_NAME, protoContact
.getProtocolProvider().getAccountID().getAccountUniqueID());
if(logger.isInfoEnabled()
&& protoContact.getParentContactGroup() == null)
{
if (logger.isInfoEnabled())
logger.info("the following contact looks weird:" + protoContact);
if (logger.isInfoEnabled())
logger.info("group:" + protoContact.getParentContactGroup());
}
if(protoContact.getParentContactGroup() == null)
return null;
protoContactElement.setAttribute(PARENT_PROTO_GROUP_UID_ATTR_NAME,
protoContact.getParentContactGroup().getUID());
// append persistent data child node
String persistentData = protoContact.getPersistentData();
if ((persistentData != null) && (persistentData.length() != 0))
{
Element persDataNode =
contactListDocument.createElement(PERSISTENT_DATA_NODE_NAME);
XMLUtils.setText(persDataNode, persistentData);
protoContactElement.appendChild(persDataNode);
}
return protoContactElement;
}
/**
* Creates a new XML <code>Element</code> corresponding to
* <tt>protoGroup</tt> or <tt>null</tt> if <tt>protoGroup</tt> is not
* eligible for serialization in its current state.
*
* @param protoGroup
* the <code>ContactGroup</code> which a corresponding XML
* <code>Element</code> is to be created for
* @return a new XML <code>Element</code> corresponding to
* <tt>protoGroup</tt> or <tt>null</tt> if <tt>protoGroup</tt> is
* not eligible for serialization in its current state
*/
private Element createProtoContactGroupNode(ContactGroup protoGroup)
{
Element protoGroupElement =
contactListDocument.createElement(PROTO_GROUP_NODE_NAME);
// set attributes
protoGroupElement.setAttribute(UID_ATTR_NAME, protoGroup.getUID());
protoGroupElement
.setAttribute(
ACCOUNT_ID_ATTR_NAME,
protoGroup
.getProtocolProvider().getAccountID().getAccountUniqueID());
/*
* The Javadoc on ContactGroup#getParentContactGroup() states null may
* be returned. Prevent a NullPointerException.
*/
ContactGroup parentContactGroup = protoGroup.getParentContactGroup();
if (parentContactGroup != null)
protoGroupElement.setAttribute(
PARENT_PROTO_GROUP_UID_ATTR_NAME,
parentContactGroup.getUID());
// append persistent data child node
String persistentData = protoGroup.getPersistentData();
if ((persistentData != null) && (persistentData.length() != 0))
{
Element persDataNode =
contactListDocument.createElement(PERSISTENT_DATA_NODE_NAME);
XMLUtils.setText(persDataNode, persistentData);
protoGroupElement.appendChild(persDataNode);
}
return protoGroupElement;
}
/**
* Creates a meta contact node element corresponding to <tt>metaContact</tt>
* .
*
*
* @param metaContact the MetaContact that the new node is about
* @return the XML Element containing the persistent version of
* <tt>metaContact</tt>
*/
private Element createMetaContactNode(MetaContact metaContact)
{
Element metaContactElement =
this.contactListDocument.createElement(META_CONTACT_NODE_NAME);
metaContactElement
.setAttribute(UID_ATTR_NAME, metaContact.getMetaUID());
// create the display name node
Element displayNameNode =
contactListDocument
.createElement(META_CONTACT_DISPLAY_NAME_NODE_NAME);
displayNameNode.appendChild(contactListDocument
.createTextNode(metaContact.getDisplayName()));
if(((MetaContactImpl)metaContact).isDisplayNameUserDefined())
displayNameNode.setAttribute(USER_DEFINED_DISPLAY_NAME_ATTR_NAME,
Boolean.TRUE.toString());
metaContactElement.appendChild(displayNameNode);
Iterator<Contact> contacts = metaContact.getContacts();
while (contacts.hasNext())
{
Contact contact = contacts.next();
Element contactElement = createProtoContactNode(contact);
if(contactElement != null)
metaContactElement.appendChild(contactElement);
}
return metaContactElement;
}
/**
* Creates a meta contact group node element corresponding to
* <tt>metaGroup</tt>.
*
* @param metaGroup the MetaContactGroup that the new node is about
* @return the XML Element containing the persistent version of
* <tt>metaGroup</tt>
*/
private Element createMetaContactGroupNode(MetaContactGroup metaGroup)
{
Element metaGroupElement =
this.contactListDocument.createElement(GROUP_NODE_NAME);
metaGroupElement.setAttribute(GROUP_NAME_ATTR_NAME, metaGroup
.getGroupName());
metaGroupElement.setAttribute(UID_ATTR_NAME, metaGroup.getMetaUID());
// create and fill the proto groups node
Element protoGroupsElement =
this.contactListDocument.createElement(PROTO_GROUPS_NODE_NAME);
metaGroupElement.appendChild(protoGroupsElement);
Iterator<ContactGroup> protoGroups = metaGroup.getContactGroups();
while (protoGroups.hasNext())
{
ContactGroup group = protoGroups.next();
// ignore if the proto group is not persistent:
if (!group.isPersistent())
continue;
Element protoGroupEl = createProtoContactGroupNode(group);
protoGroupsElement.appendChild(protoGroupEl);
}
// create and fill the sub groups node
Element subgroupsElement =
this.contactListDocument.createElement(SUBGROUPS_NODE_NAME);
metaGroupElement.appendChild(subgroupsElement);
Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups();
while (subgroups.hasNext())
{
MetaContactGroup subgroup = subgroups.next();
Element subgroupEl = createMetaContactGroupNode(subgroup);
subgroupsElement.appendChild(subgroupEl);
}
// create and fill child contacts node
Element childContactsElement =
this.contactListDocument.createElement(CHILD_CONTACTS_NODE_NAME);
metaGroupElement.appendChild(childContactsElement);
Iterator<MetaContact> childContacts = metaGroup.getChildContacts();
while (childContacts.hasNext())
{
MetaContact metaContact = childContacts.next();
Element metaContactEl = createMetaContactNode(metaContact);
childContactsElement.appendChild(metaContactEl);
}
return metaGroupElement;
}
/**
* Indicates that a MetaContact has been successfully added to the
* MetaContact list.
*
* @param evt the MetaContactListEvent containing the corresponding contact
*/
public void metaContactAdded(MetaContactEvent evt)
{
// if the parent group is not persistent, do not do anything
// cause its missing in xml
if(!evt.getParentGroup().isPersistent())
return;
Element parentGroupNode =
findMetaContactGroupNode(evt.getParentGroup().getMetaUID());
// not sure what to do in case of null. we'll be logging an internal
// err for now and that's all.
if (parentGroupNode == null)
{
logger.error("Couldn't find parent of a newly added contact: "
+ evt.getSourceMetaContact());
if(logger.isTraceEnabled())
logger.trace("The above exception occurred with the "
+ "following stack trace: ",
new Exception());
return;
}
parentGroupNode =
XMLUtils.findChild(parentGroupNode, CHILD_CONTACTS_NODE_NAME);
Element metaContactElement =
createMetaContactNode(evt.getSourceMetaContact());
parentGroupNode.appendChild(metaContactElement);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after adding contact "
+ evt.getSourceMetaContact(), ex);
}
}
/**
* Creates XML nodes for the source metacontact group, its child meta
* contacts and associated protogroups and adds them to the xml contact
* list.
*
* @param evt the MetaContactListEvent containing the corresponding contact
*/
public void metaContactGroupAdded(MetaContactGroupEvent evt)
{
// if the group was created as an encapsulator of a non persistent proto
// group then we'll ignore it.
if (evt.getSourceProtoGroup() != null
&& !evt.getSourceProtoGroup().isPersistent())
return;
MetaContactGroup parentGroup =
evt.getSourceMetaContactGroup().getParentMetaContactGroup();
Element parentGroupNode =
findMetaContactGroupNode(parentGroup.getMetaUID());
// not sure what to do in case of null. we'll be logging an internal
// err for now and that's all.
if (parentGroupNode == null)
{
logger.error("Couldn't find parent of a newly added group: "
+ parentGroup);
return;
}
Element newGroupElement =
createMetaContactGroupNode(evt.getSourceMetaContactGroup());
Element subgroupsNode =
XMLUtils.findChild(parentGroupNode, SUBGROUPS_NODE_NAME);
subgroupsNode.appendChild(newGroupElement);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after adding contact "
+ evt.getSourceMetaContactGroup(), ex);
}
}
/**
* Removes the corresponding node from the xml document.
*
* @param evt the MetaContactGroupEvent containing the corresponding contact
*/
public void metaContactGroupRemoved(MetaContactGroupEvent evt)
{
Element metaContactGroupNode =
findMetaContactGroupNode(evt.getSourceMetaContactGroup()
.getMetaUID());
// not sure what to do in case of null. we'll be loggin an internal err
// for now and that's all.
if (metaContactGroupNode == null)
{
logger.error("Save after removing an MN group. Groupt not found: "
+ evt.getSourceMetaContactGroup());
return;
}
// remove the meta contact node.
metaContactGroupNode.getParentNode().removeChild(metaContactGroupNode);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after removing group "
+ evt.getSourceMetaContactGroup(), ex);
}
}
/**
* Moves the corresponding node from its old parent to the node
* corresponding to the new parent meta group.
*
* @param evt the MetaContactListEvent containing the corresponding contact
*/
public void metaContactMoved(MetaContactMovedEvent evt)
{
Element metaContactNode =
findMetaContactNode(evt.getSourceMetaContact().getMetaUID());
Element newParentNode =
findMetaContactGroupNode(evt.getNewParent().getMetaUID());
if (newParentNode == null)
{
logger.error("Save after metacontact moved. new parent not found: "
+ evt.getNewParent());
if(logger.isTraceEnabled())
logger.error("The above exception has occurred with the "
+"following stack trace",
new Exception());
return;
}
// in case of null this is a case of moving from non persistent group
// to a persistent one.
if(metaContactNode == null)
{
// create new node
metaContactNode = createMetaContactNode(evt.getSourceMetaContact());
}
else
{
metaContactNode.getParentNode().removeChild(metaContactNode);
}
updateParentsForMetaContactNode(metaContactNode, evt.getNewParent());
Element childContacts =
XMLUtils.findChild(newParentNode, CHILD_CONTACTS_NODE_NAME);
childContacts.appendChild(metaContactNode);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after moving "
+ evt.getSourceMetaContact(), ex);
}
}
/**
* Traverses all contact elements of the metaContactNode argument and
* updates theirs parent proto group uid-s to point to the first contact
* group that is encapsulated by the newParent meta group and that belongs
* to the same account as the contact itself.
*
* @param metaContactNode the meta contact node whose child contacts we're
* to update.
* @param newParent a reference to the <tt>MetaContactGroup</tt> where
* metaContactNode was moved.
*/
private void updateParentsForMetaContactNode(Element metaContactNode,
MetaContactGroup newParent)
{
NodeList children = metaContactNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++)
{
Node currentNode = children.item(i);
if (currentNode.getNodeType() != Node.ELEMENT_NODE
|| !currentNode.getNodeName().equals(PROTO_CONTACT_NODE_NAME))
continue;
Element contactElement = (Element) currentNode;
String attribute =
contactElement.getAttribute(PARENT_PROTO_GROUP_UID_ATTR_NAME);
if (attribute == null || attribute.trim().length() == 0)
continue;
String contactAccountID =
contactElement.getAttribute(ACCOUNT_ID_ATTR_NAME);
// find the first protogroup originating from the same account as
// the one that the current contact belongs to.
Iterator<ContactGroup> possibleParents =
newParent.getContactGroupsForAccountID(contactAccountID);
String newParentUID = possibleParents.next().getUID();
contactElement.setAttribute(PARENT_PROTO_GROUP_UID_ATTR_NAME,
newParentUID);
}
}
/**
* Removes the corresponding node from the xml document.
*
* @param evt the MetaContactListEvent containing the corresponding contact
*/
public void metaContactRemoved(MetaContactEvent evt)
{
Element metaContactNode =
findMetaContactNode(evt.getSourceMetaContact().getMetaUID());
// not sure what to do in case of null. we'll be loggin an internal err
// for now and that's all.
if (metaContactNode == null)
{
logger.error("Save after metacontact removed. Contact not found: "
+ evt.getSourceMetaContact());
return;
}
// remove the meta contact node.
metaContactNode.getParentNode().removeChild(metaContactNode);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after removing "
+ evt.getSourceMetaContact(), ex);
}
}
/**
* Changes the display name attribute of the specified meta contact node.
*
* @param evt the MetaContactListEvent containing the corresponding contact
*/
public void metaContactRenamed(MetaContactRenamedEvent evt)
{
Element metaContactNode =
findMetaContactNode(evt.getSourceMetaContact().getMetaUID());
// not sure what to do in case of null. we'll be loggin an internal err
// for now and that's all.
if (metaContactNode == null)
{
logger.error("Save after renam failed. Contact not found: "
+ evt.getSourceMetaContact());
return;
}
Element displayNameNode =
XMLUtils.findChild(metaContactNode,
META_CONTACT_DISPLAY_NAME_NODE_NAME);
if(((MetaContactImpl)evt.getSourceMetaContact())
.isDisplayNameUserDefined())
{
displayNameNode.setAttribute(USER_DEFINED_DISPLAY_NAME_ATTR_NAME,
Boolean.TRUE.toString());
}
else
{
displayNameNode.removeAttribute(
USER_DEFINED_DISPLAY_NAME_ATTR_NAME);
}
XMLUtils.setText(displayNameNode, evt.getNewDisplayName());
updatePersistentDataForMetaContact(evt.getSourceMetaContact());
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after rename of "
+ evt.getSourceMetaContact(), ex);
}
}
/**
* Updates the data stored for the contact that caused this event.
*
* @param evt the MetaContactListEvent containing the corresponding contact
*/
public void protoContactModified(ProtoContactEvent evt)
{
Element metaContactNode =
findMetaContactNode(evt.getParent().getMetaUID());
// not sure what to do in case of null. we'll be logging an internal err
// for now and that's all.
if (metaContactNode == null)
{
logger.error("Save after proto contact modification failed. "
+ "Contact not found: " + evt.getParent());
return;
}
updatePersistentDataForMetaContact(evt.getParent());
// i don't think we could do anything else in addition to updating the
// persistent data.
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error(
"Writing CL failed after rename of " + evt.getParent(), ex);
}
}
/**
* Indicates that a MetaContact has been modified.
*
* @param evt the MetaContactModifiedEvent containing the corresponding
* contact
*/
public void metaContactModified(MetaContactModifiedEvent evt)
{
String name = evt.getModificationName();
Element metaContactNode =
findMetaContactNode(evt.getSourceMetaContact().getMetaUID());
// not sure what to do in case of null. we'll be logging an internal err
// for now and that's all.
if (metaContactNode == null)
{
logger.error("Save after rename failed. Contact not found: "
+ evt.getSourceMetaContact());
return;
}
Object oldValue = evt.getOldValue();
Object newValue = evt.getNewValue();
boolean isChanged = false;
if (oldValue == null && newValue != null)
{
// indicates add
if (!(newValue instanceof String))
return;
Element detailElement =
contactListDocument
.createElement(META_CONTACT_DETAIL_NAME_NODE_NAME);
detailElement.setAttribute(DETAIL_NAME_ATTR_NAME, name);
detailElement.setAttribute(DETAIL_VALUE_ATTR_NAME,
(String) newValue);
metaContactNode.appendChild(detailElement);
isChanged = true;
}
else if (oldValue != null && newValue == null)
{
// indicates remove
if (oldValue instanceof List<?>)
{
List<?> valuesToRemove = (List<?>) oldValue;
// indicates removing multiple values at one time
List<Element> nodes =
XMLUtils.locateElements(metaContactNode,
META_CONTACT_DETAIL_NAME_NODE_NAME,
DETAIL_NAME_ATTR_NAME, name);
List<Element> nodesToRemove = new ArrayList<Element>();
for (Element e : nodes)
{
if (valuesToRemove
.contains(e.getAttribute(DETAIL_VALUE_ATTR_NAME)))
{
nodesToRemove.add(e);
}
}
for (Element e : nodesToRemove)
{
metaContactNode.removeChild(e);
}
if (nodesToRemove.size() > 0)
isChanged = true;
}
else if (oldValue instanceof String)
{
// removing one value only
List<Element> nodes =
XMLUtils.locateElements(metaContactNode,
META_CONTACT_DETAIL_NAME_NODE_NAME,
DETAIL_NAME_ATTR_NAME, name);
Element elementToRemove = null;
for (Element e : nodes)
{
if (e.getAttribute(DETAIL_VALUE_ATTR_NAME).equals(oldValue))
{
elementToRemove = e;
break;
}
}
if (elementToRemove == null)
return;
metaContactNode.removeChild(elementToRemove);
isChanged = true;
}
}
else if (oldValue != null && newValue != null)
{
// indicates change
List<Element> nodes =
XMLUtils.locateElements(metaContactNode,
META_CONTACT_DETAIL_NAME_NODE_NAME, DETAIL_NAME_ATTR_NAME,
name);
Element changedElement = null;
for (Element e : nodes)
{
if (e.getAttribute(DETAIL_VALUE_ATTR_NAME).equals(oldValue))
{
changedElement = e;
break;
}
}
if (changedElement == null)
return;
changedElement.setAttribute(DETAIL_VALUE_ATTR_NAME,
(String) newValue);
isChanged = true;
}
if (!isChanged)
return;
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after rename of "
+ evt.getSourceMetaContact(), ex);
}
}
/**
* Removes the corresponding node from the xml contact list.
*
* @param evt a reference to the corresponding <tt>ProtoContactEvent</tt>
*/
public void protoContactRemoved(ProtoContactEvent evt)
{
Element oldMcNode =
findMetaContactNode(evt.getOldParent().getMetaUID());
// not sure what to do in case of null. we'll be logging an internal err
// for now and that's all.
if (oldMcNode == null)
{
logger.error("Failed to find meta contact (old parent): "
+ oldMcNode);
return;
}
Element protoNode =
XMLUtils.locateElement(oldMcNode, PROTO_CONTACT_NODE_NAME,
PROTO_CONTACT_ADDRESS_ATTR_NAME, evt.getProtoContact()
.getAddress());
protoNode.getParentNode().removeChild(protoNode);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after removing proto contact "
+ evt.getProtoContact(), ex);
}
}
/**
* We simply ignore - we're not interested in this kind of events.
*
* @param evt the <tt>MetaContactGroupEvent</tt> containing details of this
* event.
*/
public void childContactsReordered(MetaContactGroupEvent evt)
{
// ignore - not interested in such kind of events
}
/**
* Determines the exact type of the change and acts accordingly by either
* updating group name or .
*
* @param evt the MetaContactListEvent containing the corresponding contact
*/
public void metaContactGroupModified(MetaContactGroupEvent evt)
{
MetaContactGroup mcGroup = evt.getSourceMetaContactGroup();
Element mcGroupNode = findMetaContactGroupNode(mcGroup.getMetaUID());
// not sure what to do in case of null. we'll be logging an internal err
// for now and that's all.
if (mcGroupNode == null)
{
logger.error("Failed to find meta contact group: " + mcGroup);
if (logger.isTraceEnabled())
logger.trace(
"The above error occurred with the following stack trace: ",
new Exception());
return;
}
switch (evt.getEventID())
{
case MetaContactGroupEvent.CONTACT_GROUP_RENAMED_IN_META_GROUP:
case MetaContactGroupEvent.CONTACT_GROUP_REMOVED_FROM_META_GROUP:
case MetaContactGroupEvent.CONTACT_GROUP_ADDED_TO_META_GROUP:
// the fact that a contact group was added or removed to a
// meta group may imply substantial changes in the child contacts
// and the layout of any possible subgroups, so
// to make things simple, we'll remove the existing meta contact
// group node and re-create it according to its current state.
Node parentNode = mcGroupNode.getParentNode();
parentNode.removeChild(mcGroupNode);
Element newGroupElement = createMetaContactGroupNode(mcGroup);
parentNode.appendChild(newGroupElement);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that
* was probably triggered by a net operation - we could not do
* much. so ... log and @todo one day we'll have a global error
* dispatcher
*/
logger.error(
"Writing CL failed after adding contact " + mcGroup,
ex);
}
break;
case MetaContactGroupEvent.META_CONTACT_GROUP_RENAMED:
mcGroupNode
.setAttribute(GROUP_NAME_ATTR_NAME, mcGroup.getGroupName());
break;
}
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error(
"Writing CL failed after removing proto group "
+ mcGroup.getGroupName(),
ex);
}
}
/**
* Indicates that a protocol specific <tt>Contact</tt> instance has been
* added to the list of protocol specific buddies in this
* <tt>MetaContact</tt>
*
* @param evt a reference to the corresponding <tt>ProtoContactEvent</tt>
*/
public void protoContactAdded(ProtoContactEvent evt)
{
Element mcNode = findMetaContactNode(evt.getParent().getMetaUID());
// not sure what to do in case of null. we'll be logging an internal err
// for now and that's all.
if (mcNode == null)
{
logger.error("Failed to find meta contact: " + evt.getParent());
return;
}
Element protoNode = createProtoContactNode(evt.getProtoContact());
if(protoNode == null)
{
logger.error("Failed to create proto contact node for: "
+ evt.getProtoContact());
return;
}
mcNode.appendChild(protoNode);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after adding proto contact "
+ evt.getProtoContact(), ex);
}
}
/**
* Indicates that a protocol specific <tt>Contact</tt> instance has been
* moved from within one <tt>MetaContact</tt> to another.
*
* @param evt a reference to the <tt>ProtoContactMovedEvent</tt> instance.
*/
public void protoContactMoved(ProtoContactEvent evt)
{
Element newMcNode =
findMetaContactNode(evt.getNewParent().getMetaUID());
Element oldMcNode =
findMetaContactNode(evt.getOldParent().getMetaUID());
// not sure what to do in case of null. we'll be logging an internal err
// for now and that's all.
if (oldMcNode == null)
{
logger.error("Failed to find meta contact (old parent): "
+ oldMcNode);
return;
}
if (newMcNode == null)
{
logger.error("Failed to find meta contact (old parent): "
+ newMcNode);
return;
}
Element protoNode =
XMLUtils.locateElement(oldMcNode, PROTO_CONTACT_NODE_NAME,
PROTO_CONTACT_ADDRESS_ATTR_NAME, evt.getProtoContact()
.getAddress());
protoNode.getParentNode().removeChild(protoNode);
// update parent attr and append the contact to its new parent node.
protoNode.setAttribute(PARENT_PROTO_GROUP_UID_ATTR_NAME, evt
.getProtoContact().getParentContactGroup().getUID());
newMcNode.appendChild(protoNode);
try
{
scheduleContactListStorage();
}
catch (IOException ex)
{
/**
* given we're being invoked from an event dispatch thread that was
* probably triggered by a net operation - we could not do much. so
* ... log and @todo one day we'll have a global error dispatcher
*/
logger.error("Writing CL failed after moving proto contact "
+ evt.getProtoContact(), ex);
}
}
/**
* Returns the node corresponding to the meta contact with the specified uid
* or null if no such node was found.
*
* @param metaContactUID the UID String of the meta contact whose node we
* are looking for.
* @return the node corresponding to the meta contact with the specified UID
* or null if no such contact was found in the meta contact list
* file.
*/
private Element findMetaContactNode(String metaContactUID)
{
Element root = (Element) contactListDocument.getFirstChild();
return XMLUtils.locateElement(root, META_CONTACT_NODE_NAME,
UID_ATTR_NAME, metaContactUID);
}
/**
* Returns the node corresponding to the meta contact with the specified uid
* or null if no such node was found.
*
* @param metaContactGroupUID the UID String of the meta contact whose node
* we are looking for.
* @return the node corresponding to the meta contact group with the
* specified UID or null if no such group was found in the meta
* contact list file.
*/
private Element findMetaContactGroupNode(String metaContactGroupUID)
{
Element root = (Element) contactListDocument.getFirstChild();
return XMLUtils.locateElement(root, GROUP_NODE_NAME, UID_ATTR_NAME,
metaContactGroupUID);
}
/**
* Removes the file where we store contact lists.
*/
void removeContactListFile()
{
this.contactlistFile.delete();
}
/**
* Contains details parsed out of the contact list xml file, necessary for
* creating unresolved contacts.
*/
static class StoredProtoContactDescriptor
{
String contactAddress = null;
String persistentData = null;
ContactGroup parentProtoGroup = null;
StoredProtoContactDescriptor(String contactAddress,
String persistentData, ContactGroup parentProtoGroup)
{
this.contactAddress = contactAddress;
this.persistentData = persistentData;
this.parentProtoGroup = parentProtoGroup;
}
/**
* Returns a string representation of the descriptor.
*
* @return a string representation of the descriptor.
*/
@Override
public String toString()
{
return "StoredProtocoContactDescriptor[ "
+ " contactAddress="
+ contactAddress
+ " persistenData="
+ persistentData
+ " parentProtoGroup="
+ ((parentProtoGroup == null) ? "" : parentProtoGroup
.getGroupName()) + "]";
}
/**
* Utility method that allows us to verify whether a ContactDescriptor
* corresponding to a particular contact is already in a descriptor list
* and thus eliminate duplicates.
*
* @param contactAddress the address of the contact whose descriptor we
* are looking for.
* @param list the <tt>List</tt> of
* <tt>StoredProtoContactDescriptor</tt> that we are supposed
* to search for <tt>contactAddress</tt>
* @return a <tt>StoredProtoContactDescriptor</tt> corresponding to
* <tt>contactAddress</tt> or <tt>null</tt> if no such
* descriptor exists.
*/
private static StoredProtoContactDescriptor findContactInList(
String contactAddress, List<StoredProtoContactDescriptor> list)
{
if (list != null && list.size() > 0)
{
for (StoredProtoContactDescriptor desc : list)
{
if (desc.contactAddress.equals(contactAddress))
return desc;
}
}
return null;
}
}
/**
* Indicates that a new avatar is available for a <tt>MetaContact</tt>.
* @param evt the <tt>MetaContactAvatarUpdateEvent</tt> containing details
* of this event
*/
public void metaContactAvatarUpdated(MetaContactAvatarUpdateEvent evt)
{
// TODO Store MetaContact avatar.
}
}