diff options
author | Damian Minkov <damencho@jitsi.org> | 2007-01-12 17:56:58 +0000 |
---|---|---|
committer | Damian Minkov <damencho@jitsi.org> | 2007-01-12 17:56:58 +0000 |
commit | 7c6ccbd03b54d59913b45202f06bf053fe730f61 (patch) | |
tree | 5c1fd10f7382d1b43f2c67029ec0cdbafd260172 | |
parent | 44d48d13eebcb30fbfedd84424c04820aabebba7 (diff) | |
download | jitsi-7c6ccbd03b54d59913b45202f06bf053fe730f61.zip jitsi-7c6ccbd03b54d59913b45202f06bf053fe730f61.tar.gz jitsi-7c6ccbd03b54d59913b45202f06bf053fe730f61.tar.bz2 |
Yahoo Protocol Provider Implementation
49 files changed, 9432 insertions, 18 deletions
@@ -332,7 +332,7 @@ </condition> <!-- Prepare the logging.properties file for macosx --> - <exec executable="/usr/bin/sed" + <exec executable="/usr/bin/sed" dir="${basedir}" input="${inst.resrc}/logging.properties" output="${macosx.resrc.dir}/logging.properties"> @@ -393,7 +393,7 @@ <include name="${bundles.dest.macosx}/*.jar" /> <exclude name="${bundles.dest}/*-slick.jar" /> </jarfileset> - <javafilelist dir="${macosx.resrc.dir}" + <javafilelist dir="${macosx.resrc.dir}" files="logging.properties"/> <javafilelist dir="${macosx.resrc.dir}" files="felix.client.run.properties"/> @@ -639,7 +639,7 @@ <sysproperty key="java.util.logging.config.file" value="lib/logging.properties"/> - <sysproperty key="net.java.preferIPv6Addresses" + <sysproperty key="java.net.preferIPv6Addresses" value="true"/> <!-- Setting properties necessary for dependencies on native libs.--> @@ -719,10 +719,11 @@ bundle-fileaccess-slick,bundle-media,bundle-media-slick, bundle-protocol,bundle-icq,bundle-icq-slick,bundle-mock, bundle-jabber,bundle-jabber-slick,bundle-swing-ui, - bundle-msn,bundle-msn-slick, + bundle-msn,bundle-msn-slick,bundle-yahoo,bundle-yahoo-slick, bundle-contactlist,meta-contactlist,meta-contactlist-slick, bundle-plugin-icqaccregwizz,bundle-plugin-jabberaccregwizz, bundle-plugin-msnaccregwizz,bundle-plugin-sipaccregwizz, + bundle-plugin-yahooaccregwizz, bundle-version,bundle-version-impl,bundle-shutdown, bundle-growlnotification"/> @@ -1068,6 +1069,29 @@ javax.swing.event, javax.swing.border"/> <zipfileset src="${lib}/commons-logging.jar" prefix=""/> </jar> </target> + + <!-- BUNDLE-YAHOO --> + <target name="bundle-yahoo"> + <!-- Creates a bundle containing the yahoo impl of the protocol provider.--> + <jar compress="false" destfile="${bundles.dest}/protocol-yahoo.jar" + manifest="src/net/java/sip/communicator/impl/protocol/yahoo/yahoo.provider.manifest.mf"> + <zipfileset dir="${dest}/net/java/sip/communicator/impl/protocol/yahoo" + prefix="net/java/sip/communicator/impl/protocol/yahoo"/> + <zipfileset src="${lib}/ymsg_network_v0_61.jar" prefix=""/> + </jar> + </target> + + <!-- BUNDLE-YAHOO-SLICK --> + <!-- Creates a bundle containing the slick for the Yahoo protocol provider.--> + <target name="bundle-yahoo-slick"> + + <jar compress="false" destfile="${bundles.dest}/protocol-yahoo-slick.jar" + manifest="test/net/java/sip/communicator/slick/protocol/yahoo/yahoo.provider.slick.manifest.mf"> + <zipfileset dir="${dest}/net/java/sip/communicator/slick/protocol/yahoo" + prefix="net/java/sip/communicator/slick/protocol/yahoo"/> + <zipfileset src="${lib}/ymsg_network_v0_61.jar" prefix=""/> + </jar> + </target> <!-- BUNDLE-SWING-UI --> @@ -1141,6 +1165,16 @@ javax.swing.event, javax.swing.border"/> prefix="net/java/sip/communicator/plugin/msnaccregwizz"/> </jar> </target> + + <!-- BUNDLE-PLUGIN-YAHOOACCREGWIZZ --> + <target name="bundle-plugin-yahooaccregwizz"> + <!-- Creates a bundle for the plugin Yahoo Account Registration Wizard.--> + <jar compress="false" destfile="${bundles.dest}/yahooaccregwizz.jar" + manifest="src/net/java/sip/communicator/plugin/yahooaccregwizz/yahooaccregwizz.manifest.mf"> + <zipfileset dir="${dest}/net/java/sip/communicator/plugin/yahooaccregwizz" + prefix="net/java/sip/communicator/plugin/yahooaccregwizz"/> + </jar> + </target> <!-- BUNDLE-PLUGIN-SIPACCREGWIZZ --> <target name="bundle-plugin-sipaccregwizz"> diff --git a/lib/accounts.properties.template b/lib/accounts.properties.template index 862bd50..ac439c3 100644 --- a/lib/accounts.properties.template +++ b/lib/accounts.properties.template @@ -185,3 +185,35 @@ accounts.msn.CONTACT_LIST= # list of msn accounts to notify during testing (optional) accounts.reporting.MSN_REPORT_LIST= + +# YAHOO PROPERTIES + +# YAHOO ACCOUNT 1 +# The username needed to log onto the server +accounts.yahoo.account1.USER_ID= + +# The password (in plain text) needed to log the user specified in USER_ID +# on the server +accounts.yahoo.account1.PASSWORD= + +# YAHOO ACCOUNT 2 + +# The username needed to log onto the server +accounts.yahoo.account2.USER_ID= + +# The password (in plain text) needed to log the user specified in USER_ID +# on the server +accounts.yahoo.account2.PASSWORD= + +# The following describes the contact list that we will store on the Msn +# server for the tested account implementation. The value of the CONTACT_LIST +# property must be a space separated string containing elements each of which +# is in the format: GroupName.UIN +# +# +# VERY IMPORTANT!!! All other users in this contact list will be removed and +# the contacts and contact groups enumerated beneath will be added instead +accounts.yahoo.CONTACT_LIST= + +# list of yahoo accounts to notify during testing (optional) +accounts.reporting.YAHOO_REPORT_LIST= diff --git a/lib/felix.client.run.properties b/lib/felix.client.run.properties index 467ff42..139279d 100644 --- a/lib/felix.client.run.properties +++ b/lib/felix.client.run.properties @@ -57,6 +57,7 @@ felix.auto.start.3= \ reference:file:sc-bundles/protocol-sip.jar \ reference:file:sc-bundles/protocol-jabber.jar \ reference:file:sc-bundles/protocol-msn.jar \ + reference:file:sc-bundles/protocol-yahoo.jar \ reference:file:sc-bundles/netaddr.jar \ reference:file:sc-bundles/meta-cl.jar @@ -74,6 +75,7 @@ felix.auto.start.4= \ reference:file:sc-bundles/sipaccregwizz.jar \ reference:file:sc-bundles/jabberaccregwizz.jar \ reference:file:sc-bundles/msnaccregwizz.jar \ + reference:file:sc-bundles/yahooaccregwizz.jar \ reference:file:sc-bundles/shutdown.jar # Uncomment the following lines if you want to run the architect viewer diff --git a/lib/felix.unit.test.properties b/lib/felix.unit.test.properties index 0cdbcd6..101eb43 100644 --- a/lib/felix.unit.test.properties +++ b/lib/felix.unit.test.properties @@ -57,6 +57,7 @@ felix.auto.start.4= \ file:sc-bundles/protocol-jabber.jar \ file:sc-bundles/protocol-msn.jar \ file:sc-bundles/protocol-sip.jar \ + file:sc-bundles/protocol-yahoo.jar \ file:sc-bundles/media.jar \ file:sc-bundles/meta-cl.jar \ file:sc-bundles/msghistory.jar \ @@ -76,6 +77,7 @@ felix.auto.start.5= \ file:sc-bundles/protocol-sip-slick.jar \ file:sc-bundles/protocol-jabber-slick.jar \ file:sc-bundles/protocol-msn-slick.jar \ + file:sc-bundles/protocol-yahoo-slick.jar \ file:sc-bundles/msghistory-slick.jar \ file:sc-bundles/callhistory-slick.jar diff --git a/lib/ymsg_network_v0_61.jar b/lib/ymsg_network_v0_61.jar Binary files differnew file mode 100644 index 0000000..c5e3df7 --- /dev/null +++ b/lib/ymsg_network_v0_61.jar diff --git a/src/net/java/sip/communicator/impl/gui/resources/protocols/yahoo/yahoo16x16-connecting.gif b/src/net/java/sip/communicator/impl/gui/resources/protocols/yahoo/yahoo16x16-connecting.gif Binary files differnew file mode 100644 index 0000000..4f07cb7 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/resources/protocols/yahoo/yahoo16x16-connecting.gif diff --git a/src/net/java/sip/communicator/impl/gui/utils/Constants.java b/src/net/java/sip/communicator/impl/gui/utils/Constants.java index 8cda9b4..3b9b706 100755 --- a/src/net/java/sip/communicator/impl/gui/utils/Constants.java +++ b/src/net/java/sip/communicator/impl/gui/utils/Constants.java @@ -461,7 +461,7 @@ public class Constants { } else if (protocolName.equals(Constants.YAHOO)) { return ImageLoader.getAnimatedImage( - ImageLoader.ICQ_CONNECTING); + ImageLoader.YAHOO_CONNECTING); } else if (protocolName.equals(Constants.JABBER)) { return ImageLoader.getAnimatedImage( diff --git a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java index c82e869..bd70484 100644 --- a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java +++ b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java @@ -802,6 +802,11 @@ public class ImageLoader { * The Yahoo logo 16x16 icon. */ public static final ImageID YAHOO_LOGO = new ImageID("YAHOO_LOGO"); + + /** + * The ICQ "connecting" 16x16 animated icon. + */ + public static final ImageID YAHOO_CONNECTING = new ImageID("YAHOO_CONNECTING"); /** * The Jabber logo 32x32 icon. diff --git a/src/net/java/sip/communicator/impl/gui/utils/images.properties b/src/net/java/sip/communicator/impl/gui/utils/images.properties index c84f346..5009060 100644 --- a/src/net/java/sip/communicator/impl/gui/utils/images.properties +++ b/src/net/java/sip/communicator/impl/gui/utils/images.properties @@ -109,6 +109,7 @@ AIM_32x32=net/java/sip/communicator/impl/gui/resources/protocols/aim/Aim.png AIM_LOGO=net/java/sip/communicator/impl/gui/resources/protocols/aim/Aim16.png YAHOO_32x32=net/java/sip/communicator/impl/gui/resources/protocols/yahoo/Yahoo.png YAHOO_LOGO=net/java/sip/communicator/impl/gui/resources/protocols/yahoo/Yahoo16.png +YAHOO_CONNECTING=net/java/sip/communicator/impl/gui/resources/protocols/yahoo/yahoo16x16-connecting.gif JABBER_32x32=net/java/sip/communicator/impl/gui/resources/protocols/jabber/Jabber2.png JABBER_CONNECTING=net/java/sip/communicator/impl/gui/resources/protocols/jabber/jabber16x16-connecting.gif JABBER_LOGO=net/java/sip/communicator/impl/gui/resources/protocols/jabber/Jabber16.png diff --git a/src/net/java/sip/communicator/impl/protocol/msn/ServerStoredContactListMsnImpl.jbx b/src/net/java/sip/communicator/impl/protocol/msn/ServerStoredContactListMsnImpl.jbx deleted file mode 100644 index a6a1c55..0000000 --- a/src/net/java/sip/communicator/impl/protocol/msn/ServerStoredContactListMsnImpl.jbx +++ /dev/null @@ -1,12 +0,0 @@ -[PropertyInfo] -contactListModManager,net.java.sip.communicator.impl.protocol.msn.EventManager,false,false, , ,false,<default> -messenger,net.sf.jml.MsnMessenger,false,false, , ,false,<default> -msnProvider,net.java.sip.communicator.impl.protocol.msn.ProtocolProviderServiceMsnImpl,false,false, , ,false,<default> -parentOperationSet,net.java.sip.communicator.impl.protocol.msn.OperationSetPersistentPresenceMsnImpl,false,false, , ,false,<default> -rootGroup,net.java.sip.communicator.service.protocol.ContactGroup,false,false, , ,true,<default> -serverStoredGroupListeners,java.util.Vector,false,false, , ,false,<default> -[IconNames] - - - - diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/AbstractContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/AbstractContactGroupYahooImpl.java new file mode 100644 index 0000000..a4d409d --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/AbstractContactGroupYahooImpl.java @@ -0,0 +1,29 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import net.java.sip.communicator.service.protocol.*; + +/** + * The Yahoo implementation of the service.protocol.ContactGroup interface. There + * are two types of groups possible here. <tt>RootContactGroupYahooImpl</tt> + * which is the root node of the ContactList itself and + * <tt>ContactGroupYahooImpl</tt> which represents standard groups. The + * reason for having those 2 is that generally, Yahoo groups may not contain + * subgroups. A contact list on the other hand may not directly contain buddies. + * + * + * The reason for having an abstract class is only - being able to esily + * recognize our own (Yahoo) contacts. + * @author Damian Minkov + */ +public abstract class AbstractContactGroupYahooImpl + implements ContactGroup +{ + + +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ContactGroupYahooImpl.java new file mode 100644 index 0000000..6c55018 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/ContactGroupYahooImpl.java @@ -0,0 +1,473 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; +import ymsg.network.*; + +/** + * The Yahoo implementation of the ContactGroup interface. Intances of this class + * (contrary to <tt>RootContactGroupYahooImpl</tt>) may only contain buddies + * and cannot have sub groups. Note that instances of this class only use the + * corresponding smack source group for reading their names and only + * initially fill their <tt>buddies</tt> <tt>java.util.List</tt> with + * the ContactYahooImpl objects corresponding to those contained in the source + * group at the moment it is being created. They would, however, never try to + * sync or update their contents ulteriorly. This would have to be done through + * the addContact()/removeContact() methods. + * The content of buddies is created on creating of the group and when the smack + * source group is changed. + * + * @author Damian Minkov + */ +public class ContactGroupYahooImpl + extends AbstractContactGroupYahooImpl +{ + private static final Logger logger = + Logger.getLogger(ContactGroupYahooImpl.class); + + private List buddies = new LinkedList(); + private boolean isResolved = false; + + /** + * The Yahoo Group corresponding to this contact group. + */ + private YahooGroup yahooGroup = null; + + /** + * a list that would always remain empty. We only use it so that we're able + * to extract empty iterators + */ + private List dummyGroupsList = new LinkedList(); + + private String tempId = null; + + private ServerStoredContactListYahooImpl ssclCallback = null; + + /** + * Creates an Yahoo group using the specified <tt>YahooGroup</tt> as + * a source. The newly created group will always return the name of the + * underlying RosterGroup and would thus automatically adapt to changes. + * It would, however, not receive or try to poll for modifications of the + * buddies it contains and would therefore have to be updated manually by + * ServerStoredContactListImpl update will only be done if source group + * is changed. + + * @param yahooGroup the Yahoo Group correspoinding to the group + * @param groupMembers the group members that we should add to the group. + * @param ssclCallback a callback to the server stored contact list + * we're creating. + * @param isResolved a boolean indicating whether or not the group has been + * resolved against the server. + */ + ContactGroupYahooImpl( + YahooGroup yahooGroup, + Vector groupMembers, + ServerStoredContactListYahooImpl ssclCallback, + boolean isResolved) + { + this.yahooGroup = yahooGroup; + this.isResolved = isResolved; + this.ssclCallback = ssclCallback; + + Iterator iter = groupMembers.iterator(); + while(iter.hasNext()) + { + addContact( + new ContactYahooImpl( + (YahooUser)iter.next(), + ssclCallback, true, true) ); + } + } + + ContactGroupYahooImpl( + String id, + ServerStoredContactListYahooImpl ssclCallback) + { + this.tempId = id; + this.isResolved = false; + this.ssclCallback = ssclCallback; + } + + + /** + * Returns the number of <tt>Contact</tt> members of this + * <tt>ContactGroup</tt> + * + * @return an int indicating the number of <tt>Contact</tt>s, + * members of this <tt>ContactGroup</tt>. + */ + public int countContacts() + { + return buddies.size(); + } + + /** + * Returns a reference to the root group which in Yahoo is the parent of + * any other group since the protocol does not support subgroups. + * @return a reference to the root group. + */ + public ContactGroup getParentContactGroup() + { + return ssclCallback.getRootGroup(); + } + + /** + * Adds the specified contact at the specified position. + * @param contact the new contact to add to this group + * @param index the position where the new contact should be added. + */ + void addContact(int index, ContactYahooImpl contact) + { + buddies.add(index, contact); + } + + /** + * Adds the specified contact to the end of this group. + * @param contact the new contact to add to this group + */ + void addContact(ContactYahooImpl contact) + { + addContact(countContacts(), contact); + } + + + /** + * Removes the specified contact from this contact group + * @param contact the contact to remove. + */ + void removeContact(ContactYahooImpl contact) + { + removeContact(buddies.indexOf(contact)); + } + + /** + * Removes the contact with the specified index. + * @param index the index of the cntact to remove + */ + void removeContact(int index) + { + buddies.remove(index); + } + + /** + * Removes all buddies in this group and reinsterts them as specified + * by the <tt>newOrder</tt> param. Contacts not contained in the + * newOrder list are left at the end of this group. + * + * @param newOrder a list containing all contacts in the order that is + * to be applied. + * + */ + void reorderContacts(List newOrder) + { + buddies.removeAll(newOrder); + buddies.addAll(0, newOrder); + } + + /** + * Returns an Iterator over all contacts, member of this + * <tt>ContactGroup</tt>. + * + * @return a java.util.Iterator over all contacts inside this + * <tt>ContactGroup</tt>. In case the group doesn't contain any + * memebers it will return an empty iterator. + */ + public Iterator contacts() + { + return buddies.iterator(); + } + + /** + * Returns the <tt>Contact</tt> with the specified index. + * + * @param index the index of the <tt>Contact</tt> to return. + * @return the <tt>Contact</tt> with the specified index. + */ + public Contact getContact(int index) + { + return (ContactYahooImpl) buddies.get(index); + } + + /** + * Returns the <tt>Contact</tt> with the specified address or + * identifier. + * @param id the addres or identifier of the <tt>Contact</tt> we are + * looking for. + * @return the <tt>Contact</tt> with the specified id or address. + */ + public Contact getContact(String id) + { + return this.findContact(id); + } + + /** + * Returns the name of this group. + * @return a String containing the name of this group. + */ + public String getGroupName() + { + if(isResolved) + return yahooGroup.getName(); + else + return tempId; + } + + /** + * Determines whether the group may contain subgroups or not. + * + * @return always false since only the root group may contain subgroups. + */ + public boolean canContainSubgroups() + { + return false; + } + + /** + * Returns the subgroup with the specified index (i.e. always null since + * this group may not contain subgroups). + * + * @param index the index of the <tt>ContactGroup</tt> to retrieve. + * @return always null + */ + public ContactGroup getGroup(int index) + { + return null; + } + + /** + * Returns the subgroup with the specified name. + * @param groupName the name of the <tt>ContactGroup</tt> to retrieve. + * @return the <tt>ContactGroup</tt> with the specified index. + */ + public ContactGroup getGroup(String groupName) + { + return null; + } + + /** + * Returns an empty iterator. Subgroups may only be present in the root + * group. + * + * @return an empty iterator + */ + public Iterator subgroups() + { + return dummyGroupsList.iterator(); + } + + /** + * Returns the number of subgroups contained by this group, which is + * always 0 since sub groups in the protocol may only be contained + * by the root group - <tt>RootContactGroupImpl</tt>. + * @return a 0 int. + */ + public int countSubgroups() + { + return 0; + } + + /** + * Returns a hash code value for the object, which is actually the hashcode + * value of the groupname. + * + * @return a hash code value for this ContactGroup. + */ + public int hashCode() + { + return getGroupName().hashCode(); + } + + /** + * Indicates whether some other object is "equal to" this group. + * + * @param obj the reference object with which to compare. + * @return <tt>true</tt> if this object is the same as the obj + * argument; <tt>false</tt> otherwise. + */ + public boolean equals(Object obj) + { + if( obj == this ) + return true; + + if (obj == null + || !(obj instanceof ContactGroupYahooImpl) ) + return false; + + if(!((ContactGroup)obj).getGroupName().equals(getGroupName())) + return false; + + //since Yahoo does not support having two groups with the same name + // at this point we could bravely state that the groups are the same + // and not bother to compare buddies. (gotta check that though) + return true; + } + + /** + * Returns the protocol provider that this group belongs to. + * @return a regerence to the ProtocolProviderService instance that this + * ContactGroup belongs to. + */ + public ProtocolProviderService getProtocolProvider() + { + return this.ssclCallback.getParentProvider(); + } + + /** + * Returns a string representation of this group, in the form + * YahooGroup.GroupName[size]{ buddy1.toString(), buddy2.toString(), ...}. + * @return a String representation of the object. + */ + public String toString() + { + StringBuffer buff = new StringBuffer("YahooGroup."); + buff.append(getGroupName()); + buff.append(", childContacts="+countContacts()+":["); + + Iterator contacts = contacts(); + while (contacts.hasNext()) + { + ContactYahooImpl contact = (ContactYahooImpl) contacts.next(); + buff.append(contact.toString()); + if(contacts.hasNext()) + buff.append(", "); + } + return buff.append("]").toString(); + } + + /** + * Returns the contact encapsulating with the spcieified name or + * null if no such contact was found. + * + * @param id the id for the contact we're looking for. + * @return the <tt>ContactYahooImpl</tt> corresponding to the specified + * screnname or null if no such contact existed. + */ + ContactYahooImpl findContact(String id) + { + Iterator contacts = contacts(); + while (contacts.hasNext()) + { + ContactYahooImpl item = (ContactYahooImpl) contacts.next(); + if(item.getAddress().equals(id)) + return item; + } + return null; + } + + /** + * Determines whether or not this contact group is being stored by the + * server. Non persistent contact groups exist for the sole purpose of + * containing non persistent contacts. + * @return true if the contact group is persistent and false otherwise. + */ + public boolean isPersistent() + { + return true; + } + + /** + * Returns null as no persistent data is required and the contact address is + * sufficient for restoring the contact. + * <p> + * @return null as no such data is needed. + */ + public String getPersistentData() + { + return null; + } + + /** + * Determines whether or not this contact group has been resolved against + * the server. Unresolved group are used when initially loading a contact + * list that has been stored in a local file until the presence operation + * set has managed to retrieve all the contact list from the server and has + * properly mapped contacts and groups to their corresponding on-line + * buddies. + * @return true if the contact has been resolved (mapped against a buddy) + * and false otherwise. + */ + public boolean isResolved() + { + return isResolved; + } + + /** + * Resolve this contact group against the specified group + * @param yahooGroup the server stored group + */ + void setResolved(YahooGroup yahooGroup) + { + if(isResolved) + return; + + this.isResolved = true; + + this.yahooGroup = yahooGroup; + + Vector contacts = yahooGroup.getMembers(); + Iterator iter = contacts.iterator(); + while(iter.hasNext()) + { + YahooUser item = (YahooUser)iter.next(); + + ContactYahooImpl contact = + ssclCallback.findContactById(item.getId()); + if(contact != null) + { + contact.setResolved(item); + + ssclCallback.fireContactResolved(this, contact); + } + else + { + ContactYahooImpl newContact = + new ContactYahooImpl(item, ssclCallback, true, true); + addContact(newContact); + + ssclCallback.fireContactAdded(this, newContact); + } + } + } + + /** + * Returns a <tt>String</tt> that uniquely represnets the group. In this we + * use the name of the group as an identifier. This may cause problems + * though, in clase the name is changed by some other application between + * consecutive runs of the sip-communicator. + * + * @return a String representing this group in a unique and persistent + * way. + */ + public String getUID() + { + return getGroupName(); + } + + /** + * The source group we are encapsulating + * @return YahooGroup + */ + YahooGroup getSourceGroup() + { + return yahooGroup; + } + + /** + * Change the source group + * change the buddies + * + * @param newGroup YahooGroup + */ + void setSourceGroup(YahooGroup newGroup) + { + this.yahooGroup = newGroup; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ContactYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ContactYahooImpl.java new file mode 100644 index 0000000..cef5594 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/ContactYahooImpl.java @@ -0,0 +1,278 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.yahooconstants.*; +import ymsg.network.*; + +/** + * The Yahoo implementation of the service.protocol.Contact interface. + * @author Damian Minkov + */ +public class ContactYahooImpl + implements Contact +{ + private YahooUser contact = null; + private boolean isLocal = false; + private byte[] image = null; + private PresenceStatus status = YahooStatusEnum.OFFLINE; + private ServerStoredContactListYahooImpl ssclCallback = null; + private boolean isPersistent = false; + private boolean isResolved = false; + + private String tempId = null; + + /** + * Creates an YahooContactImpl + * @param contact the contact object that we will be encapsulating. + * @param ssclCallback a reference to the ServerStoredContactListImpl + * instance that created us. + * @param isPersistent determines whether this contact is persistent or not. + * @param isResolved specifies whether the contact has been resolved against + * the server contact list + */ + ContactYahooImpl( + YahooUser contact, + ServerStoredContactListYahooImpl ssclCallback, + boolean isPersistent, + boolean isResolved) + { + this.contact = contact; + this.isLocal = isLocal; + this.ssclCallback = ssclCallback; + this.isPersistent = isPersistent; + this.isResolved = isResolved; + } + + ContactYahooImpl( + String id, + ServerStoredContactListYahooImpl ssclCallback, + boolean isPersistent) + { + this.tempId = id; + this.isLocal = isLocal; + this.ssclCallback = ssclCallback; + this.isPersistent = isPersistent; + this.isResolved = false; + } + + /** + * Returns the Yahoo Userid of this contact + * @return the Yahoo Userid of this contact + */ + public String getAddress() + { + if(isResolved) + return contact.getId(); + else + return tempId; + } + + /** + * Determines whether or not this Contact instance represents the user used + * by this protocol provider to connect to the service. + * + * @return true if this Contact represents us (the local user) and false + * otherwise. + */ + public boolean isLocal() + { + return isLocal; + } + + public byte[] getImage() + { + return image; + } + + /** + * Returns a hashCode for this contact. The returned hashcode is actually + * that of the Contact's Address + * @return the hashcode of this Contact + */ + public int hashCode() + { + return getAddress().hashCode(); + } + + /** + * Indicates whether some other object is "equal to" this one. + * <p> + * + * @param obj the reference object with which to compare. + * @return <tt>true</tt> if this object is the same as the obj + * argument; <tt>false</tt> otherwise. + */ + public boolean equals(Object obj) + { + if (obj == null + || !(obj instanceof ContactYahooImpl) + || !(((ContactYahooImpl)obj).getAddress().equals(getAddress()) + && ((ContactYahooImpl)obj).getProtocolProvider() + == getProtocolProvider())) + return false; + else + return true; + } + + /** + * Returns a string representation of this contact, containing most of its + * representative details. + * + * @return a string representation of this contact. + */ + public String toString() + { + StringBuffer buff = new StringBuffer("YahooContact[ id="); + buff.append(getAddress()).append("]"); + + return buff.toString(); + } + + /** + * Sets the status that this contact is currently in. The method is to + * only be called as a result of a status update received from the server. + * + * @param status the YahooStatusEnum that this contact is currently in. + */ + void updatePresenceStatus(PresenceStatus status) + { + this.status = status; + } + + /** + * Returns the status of the contact as per the last status update we've + * received for it. Note that this method is not to perform any network + * operations and will simply return the status received in the last + * status update message. If you want a reliable way of retrieving someone's + * status, you should use the <tt>queryContactStatus()</tt> method in + * <tt>OperationSetPresence</tt>. + * @return the PresenceStatus that we've received in the last status update + * pertaining to this contact. + */ + public PresenceStatus getPresenceStatus() + { + return status; + } + + /** + * Returns a String that could be used by any user interacting modules for + * referring to this contact. An alias is not necessarily unique but is + * often more human readable than an address (or id). + * @return a String that can be used for referring to this contact when + * interacting with the user. + */ + public String getDisplayName() + { + if(isResolved) + return getAddress(); + else + return tempId; + } + + /** + * Returns a reference to the contact group that this contact is currently + * a child of or null if the underlying protocol does not suppord persistent + * presence. + * @return a reference to the contact group that this contact is currently + * a child of or null if the underlying protocol does not suppord persistent + * presence. + */ + public ContactGroup getParentContactGroup() + { + return ssclCallback.findContactGroup(this); + } + + + /** + * Returns a reference to the protocol provider that created the contact. + * @return a refererence to an instance of the ProtocolProviderService + */ + public ProtocolProviderService getProtocolProvider() + { + return ssclCallback.getParentProvider(); + } + + /** + * Determines whether or not this contact is being stored by the server. + * Non persistent contacts are common in the case of simple, non-persistent + * presence operation sets. They could however also be seen in persistent + * presence operation sets when for example we have received an event + * from someone not on our contact list. Non persistent contacts are + * volatile even when coming from a persistent presence op. set. They would + * only exist until the application is closed and will not be there next + * time it is loaded. + * @return true if the contact is persistent and false otherwise. + */ + public boolean isPersistent() + { + return isPersistent; + } + + /** + * Specifies whether this contact is to be considered persistent or not. The + * method is to be used _only_ when a non-persistent contact has been added + * to the contact list and its encapsulated VolatileBuddy has been repalced + * with a standard buddy. + * @param persistent true if the buddy is to be considered persistent and + * false for volatile. + */ + void setPersistent(boolean persistent) + { + this.isPersistent = persistent; + } + + /** + * Resolve this contact against the given entry + * @param entry the server stored entry + */ + void setResolved(YahooUser entry) + { + if(isResolved) + return; + + this.isResolved = true; + contact = entry; + } + + /** + * Returns the persistent data + * @return the persistent data + */ + public String getPersistentData() + { + return null; + } + + /** + * Determines whether or not this contact has been resolved against the + * server. Unresolved contacts are used when initially loading a contact + * list that has been stored in a local file until the presence operation + * set has managed to retrieve all the contact list from the server and has + * properly mapped contacts to their on-line buddies. + * @return true if the contact has been resolved (mapped against a buddy) + * and false otherwise. + */ + public boolean isResolved() + { + return isResolved; + } + + public void setPersistentData(String persistentData) + { + } + + /** + * Get source contact + * @return YahooContact + */ + YahooUser getSourceContact() + { + return contact; + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/MessageYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/MessageYahooImpl.java new file mode 100644 index 0000000..a84c5c8 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/MessageYahooImpl.java @@ -0,0 +1,147 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import net.java.sip.communicator.service.protocol.*; + +/** + * A simple implementation of the <tt>Message</tt> interface. Right now the + * message only supports test contents and no binary data. + * + * @author Damian Minkov + */ +public class MessageYahooImpl + implements Message +{ + /** + * The content of this message. + */ + private String textContent = null; + + /** + * The content type of text. Right now only text content types (such as + * text/plain or text/html) are supported. + */ + private String contentType = null; + + /** + * The encoding under which the contennt of this message is encoded. + */ + private String contentEncoding = null; + + /** + * An String uniquely identifying this Message. + */ + private String messageUID = null; + + /** + * The subject of the message if any (may remain null). + */ + private String subject = null; + + /** + * Creates an instance of this Message with the specified parameters. + * + * @param content the text content of the message. + * @param contentType a MIME string indicating the content type of the + * <tt>content</tt> String. + * @param contentEncoding a MIME String indicating the content encoding of + * the <tt>content</tt> String. + * @param subject the subject of the message or null for empty. + */ + public MessageYahooImpl(String content, + String contentType, + String contentEncoding, + String subject) + { + this.textContent = content; + this.contentType = contentType; + this.contentEncoding = contentEncoding; + this.subject = subject; + + //generate the uid + this.messageUID = String.valueOf( System.currentTimeMillis()) + + String.valueOf(hashCode()); + + } + + /** + * Returns the content of this message if representable in text form or + * null if this message does not contain text data. + * + * @return a String containing the content of this message or null if + * the message does not contain data representable in text form. + */ + public String getContent() + { + return textContent; + } + + /** + * Returns the MIME type for the message content. + * + * @return a String containing the mime type of the message contant. + */ + public String getContentType() + { + return contentType; + } + + /** + * Returns the MIME content encoding of this message. + * + * @return a String indicating the MIME encoding of this message. + */ + public String getEncoding() + { + return contentEncoding; + } + + /** + * Returns a unique identifier of this message. + * + * @return a String that uniquely represents this message in the scope + * of this protocol. + */ + public String getMessageUID() + { + return messageUID; + } + + /** + * Get the raw/binary content of an instant message. + * + * @return a byte[] array containing message bytes. + */ + public byte[] getRawData() + { + return getContent().getBytes(); + } + + /** + * Returns the size of the content stored in this message. + * + * @return an int indicating the number of bytes that this message + * contains. + */ + public int getSize() + { + return getContent().length(); + } + + /** + * Returns the subject of this message or null if the message contains no + * subject. + * + * @return the subject of this message or null if the message contains + * no subject. + */ + public String getSubject() + { + return subject; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetBasicInstantMessagingYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetBasicInstantMessagingYahooImpl.java new file mode 100644 index 0000000..170ce2c --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetBasicInstantMessagingYahooImpl.java @@ -0,0 +1,303 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.io.*; +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.yahooconstants.*; +import net.java.sip.communicator.util.*; + +import ymsg.network.*; +import ymsg.network.event.*; + +/** + * A straightforward implementation of the basic instant messaging operation + * set. + * + * @author Damian Minkov + */ +public class OperationSetBasicInstantMessagingYahooImpl + implements OperationSetBasicInstantMessaging +{ + private static final Logger logger = + Logger.getLogger(OperationSetBasicInstantMessagingYahooImpl.class); + + /** + * A list of listeneres registered for message events. + */ + private Vector messageListeners = new Vector(); + + /** + * The provider that created us. + */ + private ProtocolProviderServiceYahooImpl yahooProvider = null; + + /** + * A reference to the persistent presence operation set that we use + * to match incoming messages to <tt>Contact</tt>s and vice versa. + */ + private OperationSetPersistentPresenceYahooImpl opSetPersPresence = null; + + /** + * Creates an instance of this operation set. + * @param provider a ref to the <tt>ProtocolProviderServiceImpl</tt> + * that created us and that we'll use for retrieving the underlying aim + * connection. + */ + OperationSetBasicInstantMessagingYahooImpl( + ProtocolProviderServiceYahooImpl provider) + { + this.yahooProvider = provider; + provider.addRegistrationStateChangeListener(new RegistrationStateListener()); + } + + /** + * Registeres a MessageListener with this operation set so that it gets + * notifications of successful message delivery, failure or reception of + * incoming messages.. + * + * @param listener the <tt>MessageListener</tt> to register. + */ + public void addMessageListener(MessageListener listener) + { + synchronized(messageListeners) + { + if(!messageListeners.contains(listener)) + { + this.messageListeners.add(listener); + } + } + } + + /** + * Unregisteres <tt>listener</tt> so that it won't receive any further + * notifications upon successful message delivery, failure or reception of + * incoming messages.. + * + * @param listener the <tt>MessageListener</tt> to unregister. + */ + public void removeMessageListener(MessageListener listener) + { + synchronized(messageListeners) + { + this.messageListeners.remove(listener); + } + } + + /** + * Determines wheter the protocol provider (or the protocol itself) support + * sending and receiving offline messages. Most often this method would + * return true for protocols that support offline messages and false for + * those that don't. It is however possible for a protocol to support these + * messages and yet have a particular account that does not (i.e. feature + * not enabled on the protocol server). In cases like this it is possible + * for this method to return true even when offline messaging is not + * supported, and then have the sendMessage method throw an + * OperationFailedException with code - OFFLINE_MESSAGES_NOT_SUPPORTED. + * + * @return <tt>true</tt> if the protocol supports offline messages and + * <tt>false</tt> otherwise. + */ + public boolean isOfflineMessagingSupported() + { + return true; + } + + /** + * Create a Message instance for sending arbitrary MIME-encoding content. + * + * @param content content value + * @param contentType the MIME-type for <tt>content</tt> + * @param contentEncoding encoding used for <tt>content</tt> + * @param subject a <tt>String</tt> subject or <tt>null</tt> for now subject. + * @return the newly created message. + */ + public Message createMessage(byte[] content, String contentType, + String contentEncoding, String subject) + { + return new MessageYahooImpl(new String(content), contentType + , contentEncoding, subject); + } + + /** + * Create a Message instance for sending a simple text messages with + * default (text/plain) content type and encoding. + * + * @param messageText the string content of the message. + * @return Message the newly created message + */ + public Message createMessage(String messageText) + { + return new MessageYahooImpl(messageText, DEFAULT_MIME_TYPE + , DEFAULT_MIME_ENCODING, null); + } + + /** + * Sends the <tt>message</tt> to the destination indicated by the + * <tt>to</tt> contact. + * + * @param to the <tt>Contact</tt> to send <tt>message</tt> to + * @param message the <tt>Message</tt> to send. + * @throws java.lang.IllegalStateException if the underlying stack is + * not registered and initialized. + * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an + * instance of ContactImpl. + */ + public void sendInstantMessage(Contact to, Message message) + throws IllegalStateException, IllegalArgumentException + { + assertConnected(); + + try + { + yahooProvider.getYahooSession().sendMessage( + ((ContactYahooImpl) to).getSourceContact().getId(), + message.getContent()); + + MessageDeliveredEvent msgDeliveredEvt + = new MessageDeliveredEvent( + message, to, new Date()); + + fireMessageEvent(msgDeliveredEvt); + } + catch (IOException ex) + { + logger.info("Cannot Send Message! " + ex.getMessage()); + MessageDeliveryFailedEvent evt = + new MessageDeliveryFailedEvent( + message, + to, + MessageDeliveryFailedEvent.NETWORK_FAILURE, + new Date()); + fireMessageEvent(evt); + return; + } + } + + /** + * Utility method throwing an exception if the stack is not properly + * initialized. + * @throws java.lang.IllegalStateException if the underlying stack is + * not registered and initialized. + */ + private void assertConnected() throws IllegalStateException + { + if (yahooProvider == null) + throw new IllegalStateException( + "The provider must be non-null and signed on the " + +"service before being able to communicate."); + if (!yahooProvider.isRegistered()) + throw new IllegalStateException( + "The provider must be signed on the service before " + +"being able to communicate."); + } + + /** + * Our listener that will tell us when we're registered to + */ + private class RegistrationStateListener + implements RegistrationStateChangeListener + { + /** + * The method is called by a ProtocolProvider implementation whenver + * a change in the registration state of the corresponding provider had + * occurred. + * @param evt ProviderStatusChangeEvent the event describing the status + * change. + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + logger.debug("The provider changed state from: " + + evt.getOldState() + + " to: " + evt.getNewState()); + + if (evt.getNewState() == RegistrationState.REGISTERED) + { + opSetPersPresence = (OperationSetPersistentPresenceYahooImpl) + yahooProvider.getSupportedOperationSets() + .get(OperationSetPersistentPresence.class.getName()); + + yahooProvider.getYahooSession(). + addSessionListener(new YahooMessageListener()); + } + } + } + + /** + * Delivers the specified event to all registered message listeners. + * @param evt the <tt>EventObject</tt> that we'd like delivered to all + * registered message listerners. + */ + private void fireMessageEvent(EventObject evt) + { + Iterator listeners = null; + synchronized (messageListeners) + { + listeners = new ArrayList(messageListeners).iterator(); + } + + while (listeners.hasNext()) + { + MessageListener listener + = (MessageListener) listeners.next(); + + if (evt instanceof MessageDeliveredEvent) + { + listener.messageDelivered( (MessageDeliveredEvent) evt); + } + else if (evt instanceof MessageReceivedEvent) + { + listener.messageReceived( (MessageReceivedEvent) evt); + } + else if (evt instanceof MessageDeliveryFailedEvent) + { + listener.messageDeliveryFailed( + (MessageDeliveryFailedEvent) evt); + } + } + } + + private class YahooMessageListener + extends SessionAdapter + { + public void messageReceived(SessionEvent ev) + { + handleNewMessage(ev); + } + + public void offlineMessageReceived(SessionEvent ev) + { + handleNewMessage(ev); + } + + private void handleNewMessage(SessionEvent ev) + { + Message newMessage = createMessage(ev.getMessage()); + + Contact sourceContact = opSetPersPresence. + findContactByID(ev.getFrom()); + + if(sourceContact == null) + { + logger.debug("received a message from an unknown contact: " + + ev.getFrom()); + //create the volatile contact + sourceContact = opSetPersPresence + .createVolatileContact(ev.getFrom()); + } + + MessageReceivedEvent msgReceivedEvt + = new MessageReceivedEvent( + newMessage, sourceContact , new Date() ); + + fireMessageEvent(msgReceivedEvt); + } + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetPersistentPresenceYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetPersistentPresenceYahooImpl.java new file mode 100644 index 0000000..71e6578 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetPersistentPresenceYahooImpl.java @@ -0,0 +1,1066 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.beans.*; +import java.io.*; +import java.util.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.yahooconstants.*; +import net.java.sip.communicator.util.*; +import ymsg.network.*; +import ymsg.network.event.*; + +/** + * The Yahoo implementation of a Persistent Presence Operation set. This class + * manages our own presence status as well as subscriptions for the presence + * status of our buddies. It also offers methods for retrieving and modifying + * the buddy contact list and adding listeners for changes in its layout. + * + * @author Damian Minkov + */ +public class OperationSetPersistentPresenceYahooImpl + implements OperationSetPersistentPresence +{ + private static final Logger logger = + Logger.getLogger(OperationSetPersistentPresenceYahooImpl.class); + + /** + * A callback to the Yahoo provider that created us. + */ + private ProtocolProviderServiceYahooImpl yahooProvider = null; + + /** + * Contains our current status message. Note that this field would only + * be changed once the server has confirmed the new status message and + * not immediately upon setting a new one.. + */ + private String currentStatusMessage = ""; + + /** + * The presence status that we were last notified of etnering. + * The initial one is OFFLINE + */ + private PresenceStatus currentStatus = YahooStatusEnum.OFFLINE; + + /** + * The list of listeners interested in receiving changes in our local + * presencestatus. + */ + private Vector providerPresenceStatusListeners = new Vector(); + + /** + * The list of subscription listeners interested in receiving notifications + * whenever . + */ + private Vector subscriptionListeners = new Vector(); + + /** + * The list of presence status listeners interested in receiving presence + * notifications of changes in status of contacts in our contact list. + */ + private Vector contactPresenceStatusListeners = new Vector(); + + /** + * Sometimes status changes are received before the contact list is inited + * here we store such events so we can show them correctly + */ + private Hashtable earlyStatusChange = new Hashtable(); + + /** + * The array list we use when returning from the getSupportedStatusSet() + * method. + */ + private static final ArrayList supportedPresenceStatusSet = new ArrayList(); + static{ + supportedPresenceStatusSet.add(YahooStatusEnum.AVAILABLE); + supportedPresenceStatusSet.add(YahooStatusEnum.BE_RIGHT_BACK); + supportedPresenceStatusSet.add(YahooStatusEnum.BUSY); + supportedPresenceStatusSet.add(YahooStatusEnum.IDLE); + supportedPresenceStatusSet.add(YahooStatusEnum.INVISIBLE); + supportedPresenceStatusSet.add(YahooStatusEnum.NOT_AT_DESK); + supportedPresenceStatusSet.add(YahooStatusEnum.NOT_AT_HOME); + supportedPresenceStatusSet.add(YahooStatusEnum.NOT_IN_OFFICE); + supportedPresenceStatusSet.add(YahooStatusEnum.OFFLINE); + supportedPresenceStatusSet.add(YahooStatusEnum.ON_THE_PHONE); + supportedPresenceStatusSet.add(YahooStatusEnum.ON_VACATION); + supportedPresenceStatusSet.add(YahooStatusEnum.OUT_TO_LUNCH); + supportedPresenceStatusSet.add(YahooStatusEnum.STEPPED_OUT); + } + + /** + * A map containing bindings between SIP Communicator's yahoo presence status + * instances and Yahoo status codes + */ + private static Map scToYahooModesMappings = new Hashtable(); + static{ + scToYahooModesMappings.put(YahooStatusEnum.AVAILABLE, + new Long(StatusConstants.STATUS_AVAILABLE)); + scToYahooModesMappings.put(YahooStatusEnum.BE_RIGHT_BACK, + new Long(StatusConstants.STATUS_BRB)); + scToYahooModesMappings.put(YahooStatusEnum.BUSY, + new Long(StatusConstants.STATUS_BUSY)); + scToYahooModesMappings.put(YahooStatusEnum.IDLE, + new Long(StatusConstants.STATUS_IDLE)); + scToYahooModesMappings.put(YahooStatusEnum.INVISIBLE, + new Long(StatusConstants.STATUS_INVISIBLE)); + scToYahooModesMappings.put(YahooStatusEnum.NOT_AT_DESK, + new Long(StatusConstants.STATUS_NOTATDESK)); + scToYahooModesMappings.put(YahooStatusEnum.NOT_AT_HOME, + new Long(StatusConstants.STATUS_NOTATHOME)); + scToYahooModesMappings.put(YahooStatusEnum.NOT_IN_OFFICE, + new Long(StatusConstants.STATUS_NOTINOFFICE)); + scToYahooModesMappings.put(YahooStatusEnum.OFFLINE, + new Long(StatusConstants.STATUS_OFFLINE)); + scToYahooModesMappings.put(YahooStatusEnum.ON_THE_PHONE, + new Long(StatusConstants.STATUS_ONPHONE)); + scToYahooModesMappings.put(YahooStatusEnum.ON_VACATION, + new Long(StatusConstants.STATUS_ONVACATION)); + scToYahooModesMappings.put(YahooStatusEnum.OUT_TO_LUNCH, + new Long(StatusConstants.STATUS_OUTTOLUNCH)); + scToYahooModesMappings.put(YahooStatusEnum.STEPPED_OUT, + new Long(StatusConstants.STATUS_STEPPEDOUT)); + } + + /** + * The server stored contact list that will be encapsulating smack's + * buddy list. + */ + private ServerStoredContactListYahooImpl ssContactList = null; + + public OperationSetPersistentPresenceYahooImpl( + ProtocolProviderServiceYahooImpl provider) + { + this.yahooProvider = provider; + + ssContactList = new ServerStoredContactListYahooImpl( this , provider); + + this.yahooProvider.addRegistrationStateChangeListener( + new RegistrationStateListener()); + } + + /** + * Registers a listener that would receive a presence status change event + * every time a contact, whose status we're subscribed for, changes her + * status. + * + * @param listener the listener that would received presence status + * updates for contacts. + */ + public void addContactPresenceStatusListener(ContactPresenceStatusListener + listener) + { + synchronized(contactPresenceStatusListeners){ + this.contactPresenceStatusListeners.add(listener); + } + } + + /** + * Adds a listener that would receive events upon changes of the provider + * presence status. + * + * @param listener the listener to register for changes in our + * PresenceStatus. + */ + public void addProviderPresenceStatusListener( + ProviderPresenceStatusListener listener) + { + synchronized(providerPresenceStatusListeners){ + providerPresenceStatusListeners.add(listener); + } + } + + /** + * Registers a listener that would receive events upong changes in server + * stored groups. + * + * @param listener a ServerStoredGroupChangeListener impl that would + * receive events upong group changes. + */ + public void addServerStoredGroupChangeListener(ServerStoredGroupListener + listener) + { + ssContactList.addGroupListener(listener); + } + + /** + * Registers a listener that would get notifications any time a new + * subscription was succesfully added, has failed or was removed. + * + * @param listener the SubscriptionListener to register + */ + public void addSubsciptionListener(SubscriptionListener listener) + { + synchronized(subscriptionListeners){ + subscriptionListeners.add(listener); + } + } + + /** + * Creates a group with the specified name and parent in the server + * stored contact list. + * + * @param parent the group where the new group should be created + * @param groupName the name of the new group to create. + * @throws OperationFailedException if such group already exists + */ + public void createServerStoredContactGroup(ContactGroup parent, + String groupName) + throws OperationFailedException + { + assertConnected(); + + if (!parent.canContainSubgroups()) + throw new IllegalArgumentException( + "The specified contact group cannot contain child groups. Group:" + + parent ); + + ssContactList.createGroup(groupName); + } + + /** + * Creates a non persistent contact for the specified address. This would + * also create (if necessary) a group for volatile contacts that would not + * be added to the server stored contact list. The volatile contact would + * remain in the list until it is really added to the contact list or + * until the application is terminated. + * @param id the address of the contact to create. + * @return the newly created volatile <tt>ContactImpl</tt> + */ + public ContactYahooImpl createVolatileContact(String id) + { + return ssContactList.createVolatileContact(id); + } + + /** + * Creates and returns a unresolved contact from the specified + * <tt>address</tt> and <tt>persistentData</tt>. + * + * @param address an identifier of the contact that we'll be creating. + * @param persistentData a String returned Contact's getPersistentData() + * method during a previous run and that has been persistently stored + * locally. + * @param parentGroup the group where the unresolved contact is supposed + * to belong to. + * @return the unresolved <tt>Contact</tt> created from the specified + * <tt>address</tt> and <tt>persistentData</tt> + */ + public Contact createUnresolvedContact(String address, + String persistentData, + ContactGroup parentGroup) + { + if(! (parentGroup instanceof ContactGroupYahooImpl || + parentGroup instanceof RootContactGroupYahooImpl) ) + throw new IllegalArgumentException( + "Argument is not an yahoo contact group (group=" + + parentGroup + ")"); + + ContactYahooImpl contact = + ssContactList.createUnresolvedContact(parentGroup, address); + + contact.setPersistentData(persistentData); + + return contact; + } + + /** + * Creates and returns a unresolved contact from the specified + * <tt>address</tt> and <tt>persistentData</tt>. + * + * @param address an identifier of the contact that we'll be creating. + * @param persistentData a String returned Contact's getPersistentData() + * method during a previous run and that has been persistently stored + * locally. + * @return the unresolved <tt>Contact</tt> created from the specified + * <tt>address</tt> and <tt>persistentData</tt> + */ + public Contact createUnresolvedContact(String address, + String persistentData) + { + return createUnresolvedContact( address + , persistentData + , getServerStoredContactListRoot()); + } + + /** + * Creates and returns a unresolved contact group from the specified + * <tt>address</tt> and <tt>persistentData</tt>. + * + * @param groupUID an identifier, returned by ContactGroup's + * getGroupUID, that the protocol provider may use in order to create + * the group. + * @param persistentData a String returned ContactGroups's + * getPersistentData() method during a previous run and that has been + * persistently stored locally. + * @param parentGroup the group under which the new group is to be + * created or null if this is group directly underneath the root. + * @return the unresolved <tt>ContactGroup</tt> created from the + * specified <tt>uid</tt> and <tt>persistentData</tt> + */ + public ContactGroup createUnresolvedContactGroup(String groupUID, + String persistentData, ContactGroup parentGroup) + { + return ssContactList.createUnresolvedContactGroup(groupUID); + } + + /** + * Returns a reference to the contact with the specified ID in case we + * have a subscription for it and null otherwise/ + * + * @param contactID a String identifier of the contact which we're + * seeking a reference of. + * @return a reference to the Contact with the specified + * <tt>contactID</tt> or null if we don't have a subscription for the + * that identifier. + */ + public Contact findContactByID(String contactID) + { + return ssContactList.findContactById(contactID); + } + + /** + * Returns the status message that was confirmed by the serfver + * + * @return the last status message that we have requested and the aim + * server has confirmed. + */ + public String getCurrentStatusMessage() + { + return currentStatusMessage; + } + + /** + * Returns the protocol specific contact instance representing the local + * user. + * + * @return the Contact (address, phone number, or uin) that the Provider + * implementation is communicating on behalf of. + */ + public Contact getLocalContact() + { + return null; + } + + /** + * Returns a PresenceStatus instance representing the state this provider + * is currently in. + * + * @return the PresenceStatus last published by this provider. + */ + public PresenceStatus getPresenceStatus() + { + return currentStatus; + } + + /** + * Returns the root group of the server stored contact list. + * + * @return the root ContactGroup for the ContactList stored by this + * service. + */ + public ContactGroup getServerStoredContactListRoot() + { + return ssContactList.getRootGroup(); + } + + /** + * Returns the set of PresenceStatus objects that a user of this service + * may request the provider to enter. + * + * @return Iterator a PresenceStatus array containing "enterable" status + * instances. + */ + public Iterator getSupportedStatusSet() + { + return supportedPresenceStatusSet.iterator(); + } + + /** + * Removes the specified contact from its current parent and places it + * under <tt>newParent</tt>. + * + * @param contactToMove the <tt>Contact</tt> to move + * @param newParent the <tt>ContactGroup</tt> where <tt>Contact</tt> + * would be placed. + */ + public void moveContactToGroup(Contact contactToMove, + ContactGroup newParent) + { + assertConnected(); + + if( !(contactToMove instanceof ContactYahooImpl) ) + throw new IllegalArgumentException( + "The specified contact is not an yahoo contact." + contactToMove); + if( !(newParent instanceof ContactGroupYahooImpl) ) + throw new IllegalArgumentException( + "The specified group is not an yahoo contact group." + + newParent); + + ssContactList.moveContact((ContactYahooImpl)contactToMove, + (ContactGroupYahooImpl)newParent); + } + + /** + * Requests the provider to enter into a status corresponding to the + * specified paramters. + * + * @param status the PresenceStatus as returned by + * getRequestableStatusSet + * @param statusMessage the message that should be set as the reason to + * enter that status + * @throws IllegalArgumentException if the status requested is not a + * valid PresenceStatus supported by this provider. + * @throws IllegalStateException if the provider is not currently + * registered. + * @throws OperationFailedException with code NETWORK_FAILURE if + * publishing the status fails due to a network error. + */ + public void publishPresenceStatus(PresenceStatus status, + String statusMessage) throws + IllegalArgumentException, IllegalStateException, + OperationFailedException + { + assertConnected(); + + if (!(status instanceof YahooStatusEnum)) + throw new IllegalArgumentException( + status + " is not a valid Yahoo status"); + + if(status.equals(YahooStatusEnum.OFFLINE)) + { + yahooProvider.unregister(); + return; + } + + try + { + yahooProvider.getYahooSession().setStatus( + ((Long)scToYahooModesMappings.get(status)).longValue()); + + fireProviderPresenceStatusChangeEvent(currentStatus, status); + } + catch(IOException ex) + { + throw new OperationFailedException("Failed to set Status", + OperationFailedException.NETWORK_FAILURE); + } + } + + /** + * Get the PresenceStatus for a particular contact. + * + * @param contactIdentifier the identifier of the contact whose status + * we're interested in. + * @return PresenceStatus the <tt>PresenceStatus</tt> of the specified + * <tt>contact</tt> + * @throws IllegalArgumentException if <tt>contact</tt> is not a contact + * known to the underlying protocol provider + * @throws IllegalStateException if the underlying protocol provider is + * not registered/signed on a public service. + * @throws OperationFailedException with code NETWORK_FAILURE if + * retrieving the status fails due to errors experienced during + * network communication + */ + public PresenceStatus queryContactStatus(String contactIdentifier) throws + IllegalArgumentException, IllegalStateException, + OperationFailedException + { + + ContactYahooImpl contact = ssContactList.findContactById(contactIdentifier); + if(contact == null) + { + logger.info("Contact not found id :" + contactIdentifier); + return null; + } + else + return yahooStatusToPresenceStatus(contact.getSourceContact().getStatus()); + } + + /** + * Removes the specified listener so that it won't receive any further + * updates on contact presence status changes + * + * @param listener the listener to remove. + */ + public void removeContactPresenceStatusListener( + ContactPresenceStatusListener listener) + { + synchronized(contactPresenceStatusListeners){ + contactPresenceStatusListeners.remove(listener); + } + } + + /** + * Unregisters the specified listener so that it does not receive further + * events upon changes in local presence status. + * + * @param listener ProviderPresenceStatusListener + */ + public void removeProviderPresenceStatusListener( + ProviderPresenceStatusListener listener) + { + synchronized(providerPresenceStatusListeners){ + providerPresenceStatusListeners.remove(listener); + } + } + + /** + * Removes the specified group from the server stored contact list. + * + * @param group the group to remove. + */ + public void removeServerStoredContactGroup(ContactGroup group) + { + assertConnected(); + + if( !(group instanceof ContactGroupYahooImpl) ) + throw new IllegalArgumentException( + "The specified group is not an yahoo contact group: " + group); + + ssContactList.removeGroup(((ContactGroupYahooImpl)group)); + } + + /** + * Removes the specified group change listener so that it won't receive + * any further events. + * + * @param listener the ServerStoredGroupChangeListener to remove + */ + public void removeServerStoredGroupChangeListener(ServerStoredGroupListener + listener) + { + ssContactList.removeGroupListener(listener); + } + + /** + * Removes the specified subscription listener. + * + * @param listener the listener to remove. + */ + public void removeSubscriptionListener(SubscriptionListener listener) + { + synchronized(subscriptionListeners){ + subscriptionListeners.remove(listener); + } + } + + /** + * Renames the specified group from the server stored contact list. + * + * @param group the group to rename. + * @param newName the new name of the group. + */ + public void renameServerStoredContactGroup(ContactGroup group, + String newName) + { + assertConnected(); + + if( !(group instanceof ContactGroupYahooImpl) ) + throw new IllegalArgumentException( + "The specified group is not an yahoo contact group: " + group); + + throw new UnsupportedOperationException("Renaming group not supported!"); + //ssContactList.renameGroup((ContactGroupYahooImpl)group, newName); + } + + /** + * Handler for incoming authorization requests. + * + * @param handler an instance of an AuthorizationHandler for + * authorization requests coming from other users requesting + * permission add us to their contact list. + */ + public void setAuthorizationHandler(AuthorizationHandler handler) + { + ssContactList.setAuthorizationHandler(handler); + } + + /** + * Persistently adds a subscription for the presence status of the + * contact corresponding to the specified contactIdentifier and indicates + * that it should be added to the specified group of the server stored + * contact list. + * + * @param parent the parent group of the server stored contact list + * where the contact should be added. <p> + * @param contactIdentifier the contact whose status updates we are + * subscribing for. + * @throws IllegalArgumentException if <tt>contact</tt> or + * <tt>parent</tt> are not a contact known to the underlying protocol + * provider. + * @throws IllegalStateException if the underlying protocol provider is + * not registered/signed on a public service. + * @throws OperationFailedException with code NETWORK_FAILURE if + * subscribing fails due to errors experienced during network + * communication + */ + public void subscribe(ContactGroup parent, String contactIdentifier) throws + IllegalArgumentException, IllegalStateException, + OperationFailedException + { + assertConnected(); + + if(! (parent instanceof ContactGroupYahooImpl) ) + throw new IllegalArgumentException( + "Argument is not an yahoo contact group (group=" + parent + ")"); + + ssContactList.addContact((ContactGroupYahooImpl)parent, contactIdentifier); + } + + /** + * Adds a subscription for the presence status of the contact + * corresponding to the specified contactIdentifier. + * + * @param contactIdentifier the identifier of the contact whose status + * updates we are subscribing for. <p> + * @throws IllegalArgumentException if <tt>contact</tt> is not a contact + * known to the underlying protocol provider + * @throws IllegalStateException if the underlying protocol provider is + * not registered/signed on a public service. + * @throws OperationFailedException with code NETWORK_FAILURE if + * subscribing fails due to errors experienced during network + * communication + */ + public void subscribe(String contactIdentifier) throws + IllegalArgumentException, IllegalStateException, + OperationFailedException + { + assertConnected(); + + ssContactList.addContact(contactIdentifier); + } + + /** + * Removes a subscription for the presence status of the specified + * contact. + * + * @param contact the contact whose status updates we are unsubscribing + * from. + * @throws IllegalArgumentException if <tt>contact</tt> is not a contact + * known to the underlying protocol provider + * @throws IllegalStateException if the underlying protocol provider is + * not registered/signed on a public service. + * @throws OperationFailedException with code NETWORK_FAILURE if + * unsubscribing fails due to errors experienced during network + * communication + */ + public void unsubscribe(Contact contact) throws IllegalArgumentException, + IllegalStateException, OperationFailedException + { + assertConnected(); + + if(! (contact instanceof ContactYahooImpl) ) + throw new IllegalArgumentException( + "Argument is not an yahoo contact (contact=" + contact + ")"); + + ssContactList.removeContact((ContactYahooImpl)contact); + } + + /** + * Converts the specified yahoo status to one of the status fields of the + * YahooStatusEnum class. + * + * @param status the yahoo Status + * @return a PresenceStatus instance representation of the yahoo Status + * parameter. The returned result is one of the YahooStatusEnum fields. + */ + YahooStatusEnum yahooStatusToPresenceStatus(long status) + { + if(status == StatusConstants.STATUS_AVAILABLE) + return YahooStatusEnum.AVAILABLE; + else if(status == StatusConstants.STATUS_BRB) + return YahooStatusEnum.BE_RIGHT_BACK; + else if(status == StatusConstants.STATUS_BUSY) + return YahooStatusEnum.BUSY; + else if(status == StatusConstants.STATUS_NOTATHOME) + return YahooStatusEnum.NOT_AT_HOME; + else if(status == StatusConstants.STATUS_NOTATDESK) + return YahooStatusEnum.NOT_AT_DESK; + else if(status == StatusConstants.STATUS_NOTINOFFICE) + return YahooStatusEnum.NOT_IN_OFFICE; + else if(status == StatusConstants.STATUS_ONPHONE) + return YahooStatusEnum.ON_THE_PHONE; + else if(status == StatusConstants.STATUS_ONVACATION) + return YahooStatusEnum.ON_VACATION; + else if(status == StatusConstants.STATUS_OUTTOLUNCH) + return YahooStatusEnum.OUT_TO_LUNCH; + else if(status == StatusConstants.STATUS_STEPPEDOUT) + return YahooStatusEnum.STEPPED_OUT; + else if(status == StatusConstants.STATUS_INVISIBLE) + return YahooStatusEnum.INVISIBLE; + else if(status == StatusConstants.STATUS_IDLE) + return YahooStatusEnum.IDLE; + else if(status == StatusConstants.STATUS_OFFLINE) + return YahooStatusEnum.OFFLINE; + // Yahoo supports custom statuses so if such is set just return available + else + return YahooStatusEnum.OFFLINE; + } + + /** + * Utility method throwing an exception if the stack is not properly + * initialized. + * @throws java.lang.IllegalStateException if the underlying stack is + * not registered and initialized. + */ + private void assertConnected() throws IllegalStateException + { + if (yahooProvider == null) + throw new IllegalStateException( + "The provider must be non-null and signed on the yahoo " + +"service before being able to communicate."); + if (!yahooProvider.isRegistered()) + throw new IllegalStateException( + "The provider must be signed on the yahoo service before " + +"being able to communicate."); + } + + /** + * Notify all provider presence listeners of the corresponding event change + * @param oldStatus the status our stack had so far + * @param newStatus the status we have from now on + */ + void fireProviderPresenceStatusChangeEvent( + PresenceStatus oldStatus, PresenceStatus newStatus) + { + if(oldStatus.equals(newStatus)){ + logger.debug("Ignored prov stat. change evt. old==new = " + + oldStatus); + return; + } + + ProviderPresenceStatusChangeEvent evt = + new ProviderPresenceStatusChangeEvent( + yahooProvider, oldStatus, newStatus); + + currentStatus = newStatus; + + + logger.debug("Dispatching Provider Status Change. Listeners=" + + providerPresenceStatusListeners.size() + + " evt=" + evt); + + Iterator listeners = null; + synchronized (providerPresenceStatusListeners) + { + listeners = new ArrayList(providerPresenceStatusListeners).iterator(); + } + + while (listeners.hasNext()) + { + ProviderPresenceStatusListener listener + = (ProviderPresenceStatusListener) listeners.next(); + + listener.providerStatusChanged(evt); + } + } + + /** + * Notify all provider presence listeners that a new status message has + * been set. + * @param oldStatusMessage the status message our stack had so far + * @param newStatusMessage the status message we have from now on + */ + private void fireProviderStatusMessageChangeEvent( + String oldStatusMessage, String newStatusMessage) + { + + PropertyChangeEvent evt = new PropertyChangeEvent( + yahooProvider, ProviderPresenceStatusListener.STATUS_MESSAGE, + oldStatusMessage, newStatusMessage); + + logger.debug("Dispatching stat. msg change. Listeners=" + + providerPresenceStatusListeners.size() + + " evt=" + evt); + + Iterator listeners = null; + synchronized (providerPresenceStatusListeners) + { + listeners = new ArrayList(providerPresenceStatusListeners).iterator(); + } + + while (listeners.hasNext()) + { + ProviderPresenceStatusListener listener = + (ProviderPresenceStatusListener) listeners.next(); + + listener.providerStatusMessageChanged(evt); + } + } + + /** + * Statuses have been received durring login process + * so we will init them once we are logged in + */ + private void initContactStatuses() + { + YahooGroup[] groups = yahooProvider.getYahooSession().getGroups(); + + for (int i = 0; i < groups.length; i++) + { + YahooGroup item = groups[i]; + Iterator iter = item.getMembers().iterator(); + while(iter.hasNext()) + { + YahooUser user = (YahooUser)iter.next(); + + ContactYahooImpl sourceContact = + ssContactList.findContactById(user.getId()); + + if(sourceContact != null) + handleContactStatusChange(sourceContact, user.getStatus()); + } + } + } + + /** + * Our listener that will tell us when we're registered to server + * and is ready to accept us as a listener. + */ + private class RegistrationStateListener + implements RegistrationStateChangeListener + { + /** + * The method is called by a ProtocolProvider implementation whenver + * a change in the registration state of the corresponding provider had + * occurred. + * @param evt ProviderStatusChangeEvent the event describing the status + * change. + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + logger.debug("The yahoo provider changed state from: " + + evt.getOldState() + + " to: " + evt.getNewState()); + + if(evt.getNewState() == RegistrationState.REGISTERED) + { + yahooProvider.getYahooSession(). + addSessionListener(new StatusChangedListener()); + + ssContactList.setYahooSession(yahooProvider.getYahooSession()); + + initContactStatuses(); + } + else if(evt.getNewState() == RegistrationState.UNREGISTERED + || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED + || evt.getNewState() == RegistrationState.CONNECTION_FAILED) + { + //since we are disconnected, we won't receive any further status + //updates so we need to change by ourselves our own status as + //well as set to offline all contacts in our contact list that + //were online + PresenceStatus oldStatus = currentStatus; + currentStatus = YahooStatusEnum.OFFLINE; + + fireProviderPresenceStatusChangeEvent(oldStatus, + currentStatus); + + //send event notifications saying that all our buddies are + //offline. The protocol does not implement top level buddies + //nor subgroups for top level groups so a simple nested loop + //would be enough. + Iterator groupsIter = + getServerStoredContactListRoot().subgroups(); + while(groupsIter.hasNext()) + { + ContactGroupYahooImpl group + = (ContactGroupYahooImpl)groupsIter.next(); + + Iterator contactsIter = group.contacts(); + + while(contactsIter.hasNext()) + { + ContactYahooImpl contact + = (ContactYahooImpl)contactsIter.next(); + + PresenceStatus oldContactStatus + = contact.getPresenceStatus(); + + if(!oldContactStatus.isOnline()) + continue; + + contact.updatePresenceStatus(YahooStatusEnum.OFFLINE); + + fireContactPresenceStatusChangeEvent( + contact + , contact.getParentContactGroup() + , oldContactStatus, YahooStatusEnum.OFFLINE); + } + } + } + } + } + + /** + * Notify all subscription listeners of the corresponding event. + * + * @param eventID the int ID of the event to dispatch + * @param sourceContact the ContactYahooImpl instance that this event is + * pertaining to. + * @param parentGroup the ContactGroupYahooImpl under which the corresponding + * subscription is located. + */ + void fireSubscriptionEvent( int eventID, + ContactYahooImpl sourceContact, + ContactGroup parentGroup) + { + SubscriptionEvent evt = + new SubscriptionEvent(sourceContact, yahooProvider, parentGroup, + eventID); + + logger.debug("Dispatching a Subscription Event to" + +subscriptionListeners.size() + " listeners. Evt="+evt); + + Iterator listeners = null; + synchronized (subscriptionListeners) + { + listeners = new ArrayList(subscriptionListeners).iterator(); + } + + while (listeners.hasNext()) + { + SubscriptionListener listener + = (SubscriptionListener) listeners.next(); + + if (evt.getEventID() == SubscriptionEvent.SUBSCRIPTION_CREATED) + listener.subscriptionCreated(evt); + else if (evt.getEventID() == SubscriptionEvent.SUBSCRIPTION_REMOVED) + listener.subscriptionRemoved(evt); + else if (evt.getEventID() == SubscriptionEvent.SUBSCRIPTION_FAILED) + listener.subscriptionFailed(evt); + } + } + + /** + * Notify all subscription listeners of the corresponding event. + * + * @param sourceContact the ContactYahooImpl instance that this event is + * pertaining to. + * @param oldParentGroup the group that was previously a parent of the + * source contact. + * @param newParentGroup the group under which the corresponding + * subscription is currently located. + */ + void fireSubscriptionMovedEvent( ContactYahooImpl sourceContact, + ContactGroup oldParentGroup, + ContactGroup newParentGroup) + { + SubscriptionMovedEvent evt = + new SubscriptionMovedEvent(sourceContact, yahooProvider + , oldParentGroup, newParentGroup); + + logger.debug("Dispatching a Subscription Event to" + +subscriptionListeners.size() + " listeners. Evt="+evt); + + Iterator listeners = null; + synchronized (subscriptionListeners) + { + listeners = new ArrayList(subscriptionListeners).iterator(); + } + + while (listeners.hasNext()) + { + SubscriptionListener listener + = (SubscriptionListener) listeners.next(); + + listener.subscriptionMoved(evt); + } + } + + /** + * Notify all contact presence listeners of the corresponding event change + * @param contact the contact that changed its status + * @param oldStatus the status that the specified contact had so far + * @param newStatus the status that the specified contact is currently in. + * @param parentGroup the group containing the contact which caused the event + */ + private void fireContactPresenceStatusChangeEvent( + Contact contact, + ContactGroup parentGroup, + PresenceStatus oldStatus, + PresenceStatus newStatus) + { + ContactPresenceStatusChangeEvent evt = + new ContactPresenceStatusChangeEvent( + contact, yahooProvider, parentGroup, oldStatus, newStatus); + + + logger.debug("Dispatching Contact Status Change. Listeners=" + + contactPresenceStatusListeners.size() + + " evt=" + evt); + + Iterator listeners = null; + synchronized (contactPresenceStatusListeners) + { + listeners = new ArrayList(contactPresenceStatusListeners).iterator(); + } + + while (listeners.hasNext()) + { + ContactPresenceStatusListener listener + = (ContactPresenceStatusListener) listeners.next(); + + listener.contactPresenceStatusChanged(evt); + } + } + + void handleContactStatusChange(ContactYahooImpl sourceContact, long newStat) + { + PresenceStatus oldStatus + = sourceContact.getPresenceStatus(); + + PresenceStatus newStatus = yahooStatusToPresenceStatus(newStat); + + // when old and new status are the same do nothing - no change + if(oldStatus.equals(newStatus)) + return; + + sourceContact.updatePresenceStatus(newStatus); + + ContactGroup parent + = ssContactList.findContactGroup(sourceContact); + + logger.debug("Will Dispatch the contact status event."); + fireContactPresenceStatusChangeEvent(sourceContact, parent, + oldStatus, newStatus); + } + + private class StatusChangedListener + extends SessionAdapter + { + public void friendsUpdateReceived(SessionFriendEvent evt) + { + logger.debug("Received a status update for contact " + evt); + + ContactYahooImpl sourceContact = + ssContactList.findContactById(evt.getFriend().getId()); + + if(sourceContact == null) + { + if(yahooProvider.getAccountID().getUserID(). + equals(evt.getFriend().getId())) + { + // thats my own status + logger.trace("Own status changed to " + evt.getFriend().getStatus()); + PresenceStatus oldStatus = currentStatus; + currentStatus = + yahooStatusToPresenceStatus(evt.getFriend().getStatus()); + fireProviderPresenceStatusChangeEvent(oldStatus, currentStatus); + + return; + } + // strange + else + return; + } + + handleContactStatusChange(sourceContact, evt.getFriend().getStatus()); + } + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetTypingNotificationsYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetTypingNotificationsYahooImpl.java new file mode 100644 index 0000000..6e7f4c4 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetTypingNotificationsYahooImpl.java @@ -0,0 +1,219 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.util.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; +import ymsg.network.event.*; + +/** + * Maps SIP Communicator typing notifications to those going and coming from + * smack lib. + * + * @author Damian Minkov + */ +public class OperationSetTypingNotificationsYahooImpl + implements OperationSetTypingNotifications +{ + private static final Logger logger = + Logger.getLogger(OperationSetTypingNotificationsYahooImpl.class); + + /** + * All currently registered TN listeners. + */ + private List typingNotificationsListeners = new ArrayList(); + + /** + * The provider that created us. + */ + private ProtocolProviderServiceYahooImpl yahooProvider = null; + + /** + * An active instance of the opSetPersPresence operation set. We're using + * it to map incoming events to contacts in our contact list. + */ + private OperationSetPersistentPresenceYahooImpl opSetPersPresence = null; + + /** + * @param provider a ref to the <tt>ProtocolProviderServiceImpl</tt> + * that created us and that we'll use for retrieving the underlying aim + * connection. + */ + OperationSetTypingNotificationsYahooImpl( + ProtocolProviderServiceYahooImpl provider) + { + this.yahooProvider = provider; + provider.addRegistrationStateChangeListener(new ProviderRegListener()); + } + + /** + * Adds <tt>l</tt> to the list of listeners registered for receiving + * <tt>TypingNotificationEvent</tt>s + * + * @param l the <tt>TypingNotificationsListener</tt> listener that we'd + * like to add + * method + */ + public void addTypingNotificationsListener(TypingNotificationsListener l) + { + synchronized(typingNotificationsListeners) + { + typingNotificationsListeners.add(l); + } + } + + /** + * Removes <tt>l</tt> from the list of listeners registered for receiving + * <tt>TypingNotificationEvent</tt>s + * + * @param l the <tt>TypingNotificationsListener</tt> listener that we'd + * like to remove + */ + public void removeTypingNotificationsListener(TypingNotificationsListener l) + { + synchronized(typingNotificationsListeners) + { + typingNotificationsListeners.remove(l); + } + } + + /** + * Delivers a <tt>TypingNotificationEvent</tt> to all registered listeners. + * @param sourceContact the contact who has sent the notification. + * @param evtCode the code of the event to deliver. + */ + private void fireTypingNotificationsEvent(Contact sourceContact + ,int evtCode) + { + logger.debug("Dispatching a TypingNotif. event to " + + typingNotificationsListeners.size()+" listeners. Contact " + + sourceContact.getAddress() + " has now a typing status of " + + evtCode); + + TypingNotificationEvent evt = new TypingNotificationEvent( + sourceContact, evtCode); + + Iterator listeners = null; + synchronized (typingNotificationsListeners) + { + listeners = new ArrayList(typingNotificationsListeners).iterator(); + } + + while (listeners.hasNext()) + { + TypingNotificationsListener listener + = (TypingNotificationsListener) listeners.next(); + + listener.typingNotificationReceifed(evt); + } + } + + /** + * Sends a notification to <tt>notifiedContatct</tt> that we have entered + * <tt>typingState</tt>. + * + * @param notifiedContact the <tt>Contact</tt> to notify + * @param typingState the typing state that we have entered. + * + * @throws java.lang.IllegalStateException if the underlying stack is + * not registered and initialized. + * @throws java.lang.IllegalArgumentException if <tt>notifiedContact</tt> is + * not an instance belonging to the underlying implementation. + */ + public void sendTypingNotification(Contact notifiedContact, int typingState) + throws IllegalStateException, IllegalArgumentException + { + assertConnected(); + + if( !(notifiedContact instanceof ContactYahooImpl) ) + throw new IllegalArgumentException( + "The specified contact is not an yahoo contact." + notifiedContact); + + if(typingState == OperationSetTypingNotifications.STATE_TYPING) + { + yahooProvider.getYahooSession(). + keyTyped(notifiedContact.getAddress()); + } + } + + /** + * Utility method throwing an exception if the stack is not properly + * initialized. + * @throws java.lang.IllegalStateException if the underlying stack is + * not registered and initialized. + */ + private void assertConnected() throws IllegalStateException + { + if (yahooProvider == null) + throw new IllegalStateException( + "The yahoo provider must be non-null and signed on the " + +"service before being able to communicate."); + if (!yahooProvider.isRegistered()) + throw new IllegalStateException( + "The yahoo provider must be signed on the service before " + +"being able to communicate."); + } + + private class TypingListener + extends SessionAdapter + { + public void notifyReceived(SessionNotifyEvent evt) + { + if(evt.isTyping()) + { + String typingUserID = evt.getFrom(); + + if(typingUserID != null) + { + Contact sourceContact = + opSetPersPresence.findContactByID(typingUserID); + + if(sourceContact == null) + return; + + // typing on + if(evt.getMode() == 1) + fireTypingNotificationsEvent(sourceContact, STATE_TYPING); + else + fireTypingNotificationsEvent(sourceContact, STATE_STOPPED); + } + } + } + } + + /** + * Our listener that will tell us when we're registered and + * ready to accept us as a listener. + */ + private class ProviderRegListener + implements RegistrationStateChangeListener + { + /** + * The method is called by a ProtocolProvider implementation whenver + * a change in the registration state of the corresponding provider had + * occurred. + * @param evt ProviderStatusChangeEvent the event describing the status + * change. + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + logger.debug("The provider changed state from: " + + evt.getOldState() + + " to: " + evt.getNewState()); + if (evt.getNewState() == RegistrationState.REGISTERED) + { + opSetPersPresence = (OperationSetPersistentPresenceYahooImpl) + yahooProvider.getSupportedOperationSets() + .get(OperationSetPersistentPresence.class.getName()); + + yahooProvider.getYahooSession().addSessionListener(new TypingListener()); + } + } + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderFactoryYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderFactoryYahooImpl.java new file mode 100644 index 0000000..b037a6f --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderFactoryYahooImpl.java @@ -0,0 +1,264 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.util.*; + +import org.osgi.framework.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * The Yahoo implementation of the ProtocolProviderFactory. + * @author Damian Minkov + */ +public class ProtocolProviderFactoryYahooImpl + extends ProtocolProviderFactory +{ + /** + * The table that we store our accounts in. + */ + private Hashtable registeredAccounts = new Hashtable(); + + /** + * Creates an instance of the ProtocolProviderFactoryYahooImpl. + */ + protected ProtocolProviderFactoryYahooImpl() + { + } + + /** + * Returns a copy of the list containing all accounts currently + * registered in this protocol provider. + * + * @return a copy of the llist containing all accounts currently installed + * in the protocol provider. + */ + public ArrayList getRegisteredAccounts() + { + return new ArrayList(registeredAccounts.keySet()); + } + + /** + * Returns the ServiceReference for the protocol provider corresponding to + * the specified accountID or null if the accountID is unknown. + * @param accountID the accountID of the protocol provider we'd like to get + * @return a ServiceReference object to the protocol provider with the + * specified account id and null if the account id is unknwon to the + * provider factory. + */ + public ServiceReference getProviderForAccount(AccountID accountID) + { + ServiceRegistration registration + = (ServiceRegistration)registeredAccounts.get(accountID); + + return (registration == null ) + ? null + : registration.getReference(); + } + + /** + * Initializes and creates an account corresponding to the specified + * accountProperties and registers the resulting ProtocolProvider in the + * <tt>context</tt> BundleContext parameter. This method has a persistent + * effect. Once created the resulting account will remain installed until + * removed through the uninstall account method. + * + * @param userIDStr the user identifier for the new account + * @param accountProperties a set of protocol (or implementation) + * specific properties defining the new account. + * @return the AccountID of the newly created account + */ + public AccountID installAccount( String userIDStr, + Map accountProperties) + { + BundleContext context + = YahooActivator.getBundleContext(); + if (context == null) + throw new NullPointerException("The specified BundleContext was null"); + + if (userIDStr == null) + throw new NullPointerException("The specified AccountID was null"); + + if (accountProperties == null) + throw new NullPointerException("The specified property map was null"); + + accountProperties.put(USER_ID, userIDStr); + + AccountID accountID = new YahooAccountID(userIDStr, accountProperties); + + //make sure we haven't seen this account id before. + if( registeredAccounts.containsKey(accountID) ) + throw new IllegalStateException( + "An account for id " + userIDStr + " was already installed!"); + + //first store the account and only then load it as the load generates + //an osgi event, the osgi event triggers (trhgough the UI) a call to + //the register() method and it needs to acces the configuration service + //and check for a password. + this.storeAccount( + YahooActivator.getBundleContext() + , accountID); + + accountID = loadAccount(accountProperties); + + return accountID; + } + + /** + * Initializes and creates an account corresponding to the specified + * accountProperties and registers the resulting ProtocolProvider in the + * <tt>context</tt> BundleContext parameter. + * + * @param accountProperties a set of protocol (or implementation) + * specific properties defining the new account. + * @return the AccountID of the newly created account + */ + public AccountID loadAccount( Map accountProperties) + { + BundleContext context + = YahooActivator.getBundleContext(); + if(context == null) + throw new NullPointerException("The specified BundleContext was null"); + + String userIDStr = (String)accountProperties.get(USER_ID); + + AccountID accountID = new YahooAccountID(userIDStr, accountProperties); + + //get a reference to the configuration service and register whatever + //properties we have in it. + + Hashtable properties = new Hashtable(); + properties.put(PROTOCOL, ProtocolNames.YAHOO); + properties.put(USER_ID, userIDStr); + + ProtocolProviderServiceYahooImpl yahooProtocolProvider + = new ProtocolProviderServiceYahooImpl(); + + yahooProtocolProvider.initialize(userIDStr, accountID); + + ServiceRegistration registration + = context.registerService( ProtocolProviderService.class.getName(), + yahooProtocolProvider, + properties); + + registeredAccounts.put(accountID, registration); + return accountID; + } + + + /** + * Removes the specified account from the list of accounts that this + * provider factory is handling. If the specified accountID is unknown to + * the ProtocolProviderFactory, the call has no effect and false is returned. + * This method is persistent in nature and once called the account + * corresponding to the specified ID will not be loaded during future runs + * of the project. + * + * @param accountID the ID of the account to remove. + * @return true if an account with the specified ID existed and was removed + * and false otherwise. + */ + public boolean uninstallAccount(AccountID accountID) + { + ServiceRegistration registration + = (ServiceRegistration)registeredAccounts.remove(accountID); + + if(registration == null) + return false; + + //kill the service + registration.unregister(); + + return removeStoredAccount( + YahooActivator.getBundleContext() + , accountID); + } + + /** + * Loads (and hence installs) all accounts previously stored in the + * configuration service. + */ + public void loadStoredAccounts() + { + super.loadStoredAccounts( YahooActivator.getBundleContext()); + } + + /** + * Prepares the factory for bundle shutdown. + */ + public void stop() + { + Enumeration registrations = this.registeredAccounts.elements(); + + while(registrations.hasMoreElements()) + { + ServiceRegistration reg + = ((ServiceRegistration)registrations.nextElement()); + + reg.unregister(); + } + + Enumeration idEnum = registeredAccounts.keys(); + + while(idEnum.hasMoreElements()) + { + registeredAccounts.remove(idEnum.nextElement()); + } + } + + /** + * Returns the configuraiton service property name prefix that we use to + * store properties concerning the account with the specified id. + * @param accountID the AccountID whose property name prefix we're looking + * for. + * @return the prefix of the configuration service property name that + * we're using when storing properties for the specified account. + */ + public String findAccountPrefix(AccountID accountID) + { + return super.findAccountPrefix(YahooActivator.getBundleContext() + , accountID); + } + + /** + * Saves the password for the specified account after scrambling it a bit + * so that it is not visible from first sight (Method remains highly + * insecure). + * + * @param accountID the AccountID for the account whose password we're + * storing. + * @param passwd the password itself. + * + * @throws java.lang.IllegalArgumentException if no account corresponding + * to <tt>accountID</tt> has been previously stored. + */ + public void storePassword(AccountID accountID, String passwd) + throws IllegalArgumentException + { + super.storePassword(YahooActivator.getBundleContext() + , accountID + , passwd); + } + + /** + * Returns the password last saved for the specified account. + * + * @param accountID the AccountID for the account whose password we're + * looking for.. + * + * @return a String containing the password for the specified accountID. + * + * @throws java.lang.IllegalArgumentException if no account corresponding + * to <tt>accountID</tt> has been previously stored. + */ + public String loadPassword(AccountID accountID) + throws IllegalArgumentException + { + return super.loadPassword(YahooActivator.getBundleContext() + , accountID ); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java new file mode 100644 index 0000000..eb08dc6 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java @@ -0,0 +1,494 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.io.*; +import java.util.*; +import java.nio.channels.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; +import ymsg.network.*; +import ymsg.network.event.*; + +/** + * An implementation of the protocol provider service over the Yahoo protocol + * + * @author Damian Minkov + */ +public class ProtocolProviderServiceYahooImpl + implements ProtocolProviderService +{ + private static final Logger logger = + Logger.getLogger(ProtocolProviderServiceYahooImpl.class); + + /** + * The hashtable with the operation sets that we support locally. + */ + private Hashtable supportedOperationSets = new Hashtable(); + + private YahooSession yahooSession = null; + + /** + * indicates whether or not the provider is initialized and ready for use. + */ + private boolean isInitialized = false; + + /** + * We use this to lock access to initialization. + */ + private Object initializationLock = new Object(); + + /** + * A list of all listeners registered for + * <tt>RegistrationStateChangeEvent</tt>s. + */ + private List registrationListeners = new ArrayList(); + + /** + * The identifier of the account that this provider represents. + */ + private AccountID accountID = null; + + /** + * Used when we need to re-register + */ + private SecurityAuthority authority = null; + + private OperationSetPersistentPresenceYahooImpl persistentPresence = null; + + private OperationSetTypingNotificationsYahooImpl typingNotifications = null; + + /** + * Returns the state of the registration of this protocol provider + * @return the <tt>RegistrationState</tt> that this provider is + * currently in or null in case it is in a unknown state. + */ + public RegistrationState getRegistrationState() + { + if(yahooSession != null && + yahooSession.getSessionStatus() == StatusConstants.MESSAGING) + return RegistrationState.REGISTERED; + else + return RegistrationState.UNREGISTERED; + } + + /** + * Starts the registration process. Connection details such as + * registration server, user name/number are provided through the + * configuration service through implementation specific properties. + * + * @param authority the security authority that will be used for resolving + * any security challenges that may be returned during the + * registration or at any moment while wer're registered. + * @throws OperationFailedException with the corresponding code it the + * registration fails for some reason (e.g. a networking error or an + * implementation problem). + */ + public void register(final SecurityAuthority authority) + throws OperationFailedException + { + if(authority == null) + throw new IllegalArgumentException( + "The register method needs a valid non-null authority impl " + + " in order to be able and retrieve passwords."); + + this.authority = authority; + + connectAndLogin(authority); + } + + /** + * Connects and logins to the server + * @param authority SecurityAuthority + * @throws XMPPException if we cannot connect to the server - network problem + * @throws OperationFailedException if login parameters + * as server port are not correct + */ + private void connectAndLogin(SecurityAuthority authority) + throws OperationFailedException + { + synchronized(initializationLock) + { + //verify whether a password has already been stored for this account + String password = YahooActivator. + getProtocolProviderFactory().loadPassword(getAccountID()); + + //decode + if (password == null) + { + //create a default credentials object + UserCredentials credentials = new UserCredentials(); + credentials.setUserName(getAccountID().getUserID()); + + //request a password from the user + credentials = authority.obtainCredentials(ProtocolNames.YAHOO + , credentials); + + //extract the password the user passed us. + char[] pass = credentials.getPassword(); + + // the user didn't provide us a password (canceled the operation) + if(pass == null) + { + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.UNREGISTERED, + RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); + return; + } + password = new String(pass); + + + if (credentials.isPasswordPersistent()) + { + YahooActivator.getProtocolProviderFactory() + .storePassword(getAccountID(), password); + } + } + + yahooSession = new YahooSession(); + yahooSession.addSessionListener(new YahooConnectionListener()); + + try + { + yahooSession.login(getAccountID().getUserID(), password); + + if(yahooSession.getSessionStatus()==StatusConstants.MESSAGING) + { + persistentPresence.fireProviderPresenceStatusChangeEvent( + persistentPresence.getPresenceStatus(), + persistentPresence.yahooStatusToPresenceStatus( + yahooSession.getStatus())); + + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.REGISTERED, + RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); + } + else + { + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.UNREGISTERED, + RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); + } + } + catch (LoginRefusedException ex) + { + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.AUTHENTICATION_FAILED, + RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED, null); + } + catch (IOException ex) + { + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.CONNECTION_FAILED, + RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); + } + } + } + + /** + * Ends the registration of this protocol provider with the service. + */ + public void unregister() + { + unregister(true); + } + + /** + * Unregister and fire the event if requested + * @param fireEvent boolean + */ + void unregister(boolean fireEvent) + { + RegistrationState currRegState = getRegistrationState(); + + try + { + yahooSession.logout(); + } + catch(Exception ex) + { + logger.error("Cannot logout!"); + } + + yahooSession.reset(); + + if(fireEvent) + { + fireRegistrationStateChanged( + currRegState, + RegistrationState.UNREGISTERED, + RegistrationStateChangeEvent.REASON_USER_REQUEST, null); + } + } + + /** + * Indicates whether or not this provider is signed on the service + * @return true if the provider is currently signed on (and hence online) + * and false otherwise. + */ + public boolean isRegistered() + { + return getRegistrationState().equals(RegistrationState.REGISTERED); + } + + /** + * Returns the short name of the protocol that the implementation of this + * provider is based upon (like SIP, Msn, ICQ/AIM, or others for + * example). + * + * @return a String containing the short name of the protocol this + * service is taking care of. + */ + public String getProtocolName() + { + return ProtocolNames.YAHOO; + } + + /** + * Returns an array containing all operation sets supported by the + * current implementation. + * + * @return an array of OperationSet-s supported by this protocol + * provider implementation. + */ + public Map getSupportedOperationSets() + { + return supportedOperationSets; + } + + /** + * Returns the operation set corresponding to the specified class or null + * if this operation set is not supported by the provider implementation. + * + * @param opsetClass the <tt>Class</tt> of the operation set that we're + * looking for. + * @return returns an OperationSet of the specified <tt>Class</tt> if the + * undelying implementation supports it or null otherwise. + */ + public OperationSet getOperationSet(Class opsetClass) + { + return (OperationSet)getSupportedOperationSets() + .get(opsetClass.getName()); + } + + /** + * Initialized the service implementation, and puts it in a sate where it + * could interoperate with other services. It is strongly recomended that + * properties in this Map be mapped to property names as specified by + * <tt>AccountProperties</tt>. + * + * @param screenname the account id/uin/screenname of the account that + * we're about to create + * @param accountID the identifier of the account that this protocol + * provider represents. + * + * @see net.java.sip.communicator.service.protocol.AccountID + */ + protected void initialize(String screenname, + AccountID accountID) + { + synchronized(initializationLock) + { + this.accountID = accountID; + + //initialize the presence operationset + persistentPresence = new OperationSetPersistentPresenceYahooImpl(this); + + supportedOperationSets.put( + OperationSetPersistentPresence.class.getName(), + persistentPresence); + + //register it once again for those that simply need presence + supportedOperationSets.put( OperationSetPresence.class.getName(), + persistentPresence); + + //initialize the IM operation set + OperationSetBasicInstantMessagingYahooImpl basicInstantMessaging = + new OperationSetBasicInstantMessagingYahooImpl(this); + + supportedOperationSets.put( + OperationSetBasicInstantMessaging.class.getName(), + basicInstantMessaging); + + //initialize the typing notifications operation set + typingNotifications = + new OperationSetTypingNotificationsYahooImpl(this); + + supportedOperationSets.put( + OperationSetTypingNotifications.class.getName(), + typingNotifications); + + isInitialized = true; + } + } + + /** + * Makes the service implementation close all open sockets and release + * any resources that it might have taken and prepare for + * shutdown/garbage collection. + */ + public void shutdown() + { + synchronized(initializationLock){ + unregister(false); + yahooSession = null; + isInitialized = false; + } + } + + /** + * Returns true if the provider service implementation is initialized and + * ready for use by other services, and false otherwise. + * + * @return true if the provider is initialized and ready for use and false + * otherwise + */ + public boolean isInitialized() + { + return isInitialized; + } + + /** + * Removes the specified registration state change listener so that it does + * not receive any further notifications upon changes of the + * RegistrationState of this provider. + * + * @param listener the listener to register for + * <tt>RegistrationStateChangeEvent</tt>s. + */ + public void removeRegistrationStateChangeListener( + RegistrationStateChangeListener listener) + { + synchronized(registrationListeners) + { + registrationListeners.remove(listener); + } + } + + /** + * Registers the specified listener with this provider so that it would + * receive notifications on changes of its state or other properties such + * as its local address and display name. + * + * @param listener the listener to register. + */ + public void addRegistrationStateChangeListener( + RegistrationStateChangeListener listener) + { + synchronized(registrationListeners) + { + if (!registrationListeners.contains(listener)) + registrationListeners.add(listener); + } + } + + /** + * Returns the AccountID that uniquely identifies the account represented + * by this instance of the ProtocolProviderService. + * @return the id of the account represented by this provider. + */ + public AccountID getAccountID() + { + return accountID; + } + + /** + * Returns the Yahoo<tt>Session</tt>opened by this provider + * @return a reference to the <tt>Session</tt> last opened by this + * provider. + */ + YahooSession getYahooSession() + { + return yahooSession; + } + + /** + * Creates a RegistrationStateChange event corresponding to the specified + * old and new states and notifies all currently registered listeners. + * + * @param oldState the state that the provider had before the change + * occurred + * @param newState the state that the provider is currently in. + * @param reasonCode a value corresponding to one of the REASON_XXX fields + * of the RegistrationStateChangeEvent class, indicating the reason for + * this state transition. + * @param reason a String further explaining the reason code or null if + * no such explanation is necessary. + */ + void fireRegistrationStateChanged( RegistrationState oldState, + RegistrationState newState, + int reasonCode, + String reason) + { + RegistrationStateChangeEvent event = + new RegistrationStateChangeEvent( + this, oldState, newState, reasonCode, reason); + + logger.debug("Dispatching " + event + " to " + + registrationListeners.size()+ " listeners."); + + if(newState.equals(RegistrationState.UNREGISTERED) || + newState.equals(RegistrationState.CONNECTION_FAILED)) + { + unregister(false); + yahooSession = null; + } + + Iterator listeners = null; + synchronized (registrationListeners) + { + listeners = new ArrayList(registrationListeners).iterator(); + } + + while (listeners.hasNext()) + { + RegistrationStateChangeListener listener + = (RegistrationStateChangeListener) listeners.next(); + + listener.registrationStateChanged(event); + } + + logger.trace("Done."); + } + + /** + * Listens when we are logged in the server + * or incoming exception in the lib impl. + */ + private class YahooConnectionListener + extends SessionAdapter + { + /** + * Yahoo has logged us off the system, or the connection was lost + **/ + public void connectionClosed(SessionEvent ev) + { + unregister(false); + if(isRegistered()) + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.CONNECTION_FAILED, + RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); + } + + public void inputExceptionThrown(SessionExceptionEvent ev) + { + unregister(false); + if(isRegistered()) + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.UNREGISTERED, + RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, null); + } + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/RootContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/RootContactGroupYahooImpl.java new file mode 100644 index 0000000..2ce9df8 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/RootContactGroupYahooImpl.java @@ -0,0 +1,301 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * A dummy ContactGroup implementation representing the ContactList root for + * Yahoo contact lists. + * @author Damian Minkov + */ +public class RootContactGroupYahooImpl + extends AbstractContactGroupYahooImpl +{ + private String ROOT_CONTACT_GROUP_NAME = "ContactListRoot"; + private List subGroups = new LinkedList(); + private boolean isResolved = false; + + /** + * An empty list that we use when returning an iterator. + */ + private List dummyContacts = new LinkedList(); + + private ProtocolProviderServiceYahooImpl ownerProvider = null; + + /** + * Creates a ContactGroup instance. + */ + RootContactGroupYahooImpl(){} + + /** + * Sets the currently valid provider + * @param ownerProvider ProtocolProviderServiceImpl + */ + void setOwnerProvider(ProtocolProviderServiceYahooImpl ownerProvider) + { + this.ownerProvider = ownerProvider; + } + + /** + * The ContactListRoot is the only group that can contain subgroups. + * + * @return true (always) + */ + public boolean canContainSubgroups() + { + return true; + } + + /** + * Returns the name of this group which is always + * <tt>ROOT_CONTACT_GROUP_NAME</tt>. + * + * @return a String containing the name of this group. + */ + public String getGroupName() + { + return ROOT_CONTACT_GROUP_NAME; + } + + /** + * Adds the specified group to the end of the list of sub groups. + * @param group the group to add. + */ + void addSubGroup(ContactGroupYahooImpl group) + { + subGroups.add(group); + } + + /** + * Removes the specified from the list of sub groups + * @param group the group to remove. + */ + void removeSubGroup(ContactGroupYahooImpl group) + { + removeSubGroup(subGroups.indexOf(group)); + } + + /** + * Removes the sub group with the specified index. + * @param index the index of the group to remove + */ + void removeSubGroup(int index) + { + subGroups.remove(index); + } + + /** + * Removes all contact sub groups and reinsterts them as specified + * by the <tt>newOrder</tt> param. Contact groups not contained in the + * newOrder list are left at the end of this group. + * + * @param newOrder a list containing all contact groups in the order that is + * to be applied. + * + */ + void reorderSubGroups(List newOrder) + { + subGroups.removeAll(newOrder); + subGroups.addAll(0, newOrder); + } + + /** + * Returns the number of subgroups contained by this + * <tt>RootContactGroupImpl</tt>. + * + * @return an int indicating the number of subgroups that this + * ContactGroup contains. + */ + public int countSubgroups() + { + return subGroups.size(); + } + + /** + * Returns null as this is the root contact group. + * @return null as this is the root contact group. + */ + public ContactGroup getParentContactGroup() + { + return null; + } + + /** + * Returns the subgroup with the specified index. + * + * @param index the index of the <tt>ContactGroup</tt> to retrieve. + * @return the <tt>ContactGroup</tt> with the specified index. + */ + public ContactGroup getGroup(int index) + { + return (ContactGroupYahooImpl) subGroups.get(index); + } + + /** + * Returns the subgroup with the specified name. + * @param groupName the name of the <tt>ContactGroup</tt> to retrieve. + * @return the <tt>ContactGroup</tt> with the specified index. + */ + public ContactGroup getGroup(String groupName) + { + Iterator subgroups = subgroups(); + while (subgroups.hasNext()) + { + ContactGroupYahooImpl grp = (ContactGroupYahooImpl) subgroups.next(); + + if (grp.getGroupName().equals(groupName)) + return grp; + } + + return null; + } + + /** + * Returns an iterator over the sub groups that this + * <tt>ContactGroup</tt> contains. + * + * @return a java.util.Iterator over the <tt>ContactGroup</tt> + * children of this group (i.e. subgroups). + */ + public Iterator subgroups() + { + return subGroups.iterator(); + } + + /** + * Returns the number, which is always 0, of <tt>Contact</tt> members + * of this <tt>ContactGroup</tt> + * @return an int indicating the number of <tt>Contact</tt>s, members + * of this <tt>ContactGroup</tt>. + */ + public int countContacts() + { + return dummyContacts.size(); + } + + /** + * Returns an Iterator over all contacts, member of this + * <tt>ContactGroup</tt>. + * @return a java.util.Iterator over all contacts inside this + * <tt>ContactGroup</tt> + */ + public Iterator contacts() + { + return dummyContacts.iterator(); + } + + /** + * A dummy impl of the corresponding interface method - always returns null. + * + * @param index the index of the <tt>Contact</tt> to return. + * @return the <tt>Contact</tt> with the specified index, i.e. always + * null. + */ + public Contact getContact(int index) + { + return null; + } + + /** + * Returns the <tt>Contact</tt> with the specified address or + * identifier. + * @param id the addres or identifier of the <tt>Contact</tt> we are + * looking for. + * @return the <tt>Contact</tt> with the specified id or address. + */ + public Contact getContact(String id) + { + //no contacts in the root group for this yahoo impl. + return null; + } + + + /** + * Returns a string representation of the root contact group that contains + * all subgroups and subcontacts of this group. + * + * @return a string representation of this root contact group. + */ + public String toString() + { + StringBuffer buff = new StringBuffer(getGroupName()); + buff.append(".subGroups=" + countSubgroups() + ":\n"); + + Iterator subGroups = subgroups(); + while (subGroups.hasNext()) + { + ContactGroup group = (ContactGroup) subGroups.next(); + buff.append(group.toString()); + if (subGroups.hasNext()) + buff.append("\n"); + } + return buff.toString(); + } + + /** + * Returns the protocol provider that this group belongs to. + * @return a regerence to the ProtocolProviderService instance that this + * ContactGroup belongs to. + */ + public ProtocolProviderService getProtocolProvider() + { + return this.ownerProvider; + } + + /** + * Determines whether or not this contact group is being stored by the + * server. Non persistent contact groups exist for the sole purpose of + * containing non persistent contacts. + * @return true if the contact group is persistent and false otherwise. + */ + public boolean isPersistent() + { + return true; + } + + /** + * Returns null as no persistent data is required and the group name is + * sufficient for restoring the contact. + * <p> + * @return null as no such data is needed. + */ + public String getPersistentData() + { + return null; + } + + /** + * Determines whether or not this group has been resolved against the + * server. Unresolved groups are used when initially loading a contact + * list that has been stored in a local file until the presence operation + * set has managed to retrieve all the contact list from the server and has + * properly mapped groups to their on-line buddies. + * @return true if the group has been resolved (mapped against a buddy) + * and false otherwise. + */ + public boolean isResolved() + { + return isResolved; + } + + /** + * Returns a <tt>String</tt> that uniquely represnets the group. In this we + * use the name of the group as an identifier. This may cause problems + * though, in clase the name is changed by some other application between + * consecutive runs of the sip-communicator. + * + * @return a String representing this group in a unique and persistent + * way. + */ + public String getUID() + { + return getGroupName(); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ServerStoredContactListYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ServerStoredContactListYahooImpl.java new file mode 100644 index 0000000..699fdce --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/ServerStoredContactListYahooImpl.java @@ -0,0 +1,1027 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.io.*; +import java.util.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; +import ymsg.network.*; +import ymsg.network.event.*; + +/** + * This class encapsulates the Roster class. Once created, it will + * register itself as a listener to the encapsulated Roster and modify it's + * local copy of Contacts and ContactGroups every time an event is generated + * by the underlying framework. The class would also generate + * corresponding sip-communicator events to all events coming from smack. + * + * @author Damian Minkov + */ +public class ServerStoredContactListYahooImpl +{ + private static final Logger logger = + Logger.getLogger(ServerStoredContactListYahooImpl.class); + + /** + * The name of the Volatile group + */ + private static final String VOLATILE_GROUP_NAME = "NotInContactList"; + + /** + * If there is no group and we add contact with no parent + * a default group is created with name : DEFAULT_GROUP_NAME + */ + private static final String DEFAULT_GROUP_NAME = "General"; + + /** + * The root contagroup. The container for all yahoo buddies and groups. + */ + private RootContactGroupYahooImpl rootGroup = new RootContactGroupYahooImpl(); + + /** + * The operation set that created us and that we could use when dispatching + * subscription events. + */ + private OperationSetPersistentPresenceYahooImpl parentOperationSet = null; + + /** + * The provider that is on top of us. + */ + private ProtocolProviderServiceYahooImpl yahooProvider = null; + + private YahooSession yahooSession = null; + + /** + * Listeners that would receive event notifications for changes in group + * names or other properties, removal or creation of groups. + */ + private Vector serverStoredGroupListeners = new Vector(); + + private ContactListModListenerImpl contactListModListenerImpl + = new ContactListModListenerImpl(); + + /** + * indicates whether or not the contactlist is initialized and ready. + */ + private boolean isInitialized = false; + + /** + * Handler for incoming authorization requests. + */ + private AuthorizationHandler handler = null; + + /** + * Creates a ServerStoredContactList wrapper for the specified BuddyList. + * + * @param parentOperationSet the operation set that created us and that + * we could use for dispatching subscription events + * @param provider the provider that has instantiated us. + */ + ServerStoredContactListYahooImpl( + OperationSetPersistentPresenceYahooImpl parentOperationSet, + ProtocolProviderServiceYahooImpl provider) + { + //We need to init these as early as possible to ensure that the provider + //and the operationsset would not be null in the incoming events. + this.parentOperationSet = parentOperationSet; + + this.yahooProvider = provider; + rootGroup.setOwnerProvider(provider); + + // listens for provider registered events to set the isInitialized state + // of the contact list + provider.addRegistrationStateChangeListener( + new RegistrationStateChangeListener() + { + public void registrationStateChanged( + RegistrationStateChangeEvent evt) + { + if (evt.getNewState() == RegistrationState.UNREGISTERED + || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED + || evt.getNewState() == RegistrationState.CONNECTION_FAILED) + { + isInitialized = false; + } + } + } + ); + } + + /** + * Handler for incoming authorization requests. + * + * @param handler an instance of an AuthorizationHandler for + * authorization requests coming from other users requesting + * permission add us to their contact list. + */ + public void setAuthorizationHandler(AuthorizationHandler handler) + { + this.handler = handler; + } + + /** + * Returns the root group of the contact list. + * + * @return the root ContactGroup for the ContactList + */ + public ContactGroup getRootGroup() + { + return rootGroup; + } + + /** + * Registers the specified group listener so that it would receive events + * on group modification/creation/destruction. + * @param listener the ServerStoredGroupListener to register for group events + */ + void addGroupListener(ServerStoredGroupListener listener) + { + synchronized(serverStoredGroupListeners) + { + if(!serverStoredGroupListeners.contains(listener)) + this.serverStoredGroupListeners.add(listener); + } + } + + /** + * Removes the specified group listener so that it won't receive further + * events on group modification/creation/destruction. + * @param listener the ServerStoredGroupListener to unregister + */ + void removeGroupListener(ServerStoredGroupListener listener) + { + synchronized(serverStoredGroupListeners) + { + this.serverStoredGroupListeners.remove(listener); + } + } + + /** + * Creates the corresponding event and notifies all + * <tt>ServerStoredGroupListener</tt>s that the source group has been + * removed, changed, renamed or whatever happened to it. + * @param group the ContactGroup that has been created/modified/removed + * @param eventID the id of the event to generate. + */ + private void fireGroupEvent(ContactGroupYahooImpl group, int eventID) + { + //bail out if no one's listening + if(parentOperationSet == null){ + logger.debug("No presence op. set available. Bailing out."); + return; + } + + ServerStoredGroupEvent evt = new ServerStoredGroupEvent( + group + , eventID + , parentOperationSet.getServerStoredContactListRoot() + , yahooProvider + , parentOperationSet); + + logger.trace("Will dispatch the following grp event: " + evt); + + Iterator listeners = null; + synchronized (serverStoredGroupListeners) + { + listeners = new ArrayList(serverStoredGroupListeners).iterator(); + } + + while (listeners.hasNext()) + { + ServerStoredGroupListener listener + = (ServerStoredGroupListener) listeners.next(); + + if (eventID == ServerStoredGroupEvent.GROUP_REMOVED_EVENT) + listener.groupRemoved(evt); + else if (eventID == ServerStoredGroupEvent.GROUP_RENAMED_EVENT) + listener.groupNameChanged(evt); + else if (eventID == ServerStoredGroupEvent.GROUP_CREATED_EVENT) + listener.groupCreated(evt); + else if (eventID == ServerStoredGroupEvent.GROUP_RESOLVED_EVENT) + listener.groupResolved(evt); + } + } + + /** + * Make the parent persistent presence operation set dispatch a contact + * removed event. + * @param parentGroup the group where that the removed contact belonged to. + * @param contact the contact that was removed. + */ + private void fireContactRemoved( ContactGroup parentGroup, + ContactYahooImpl contact) + { + //bail out if no one's listening + if(parentOperationSet == null){ + logger.debug("No presence op. set available. Bailing out."); + return; + } + + //dispatch + parentOperationSet.fireSubscriptionEvent( + SubscriptionEvent.SUBSCRIPTION_REMOVED, contact, parentGroup); + } + + /** + * Make the parent persistent presence operation set dispatch a subscription + * moved event. + * @param oldParentGroup the group where the source contact was located + * before being moved + * @param newParentGroup the group that the source contact is currently in. + * @param contact the contact that was added + */ + private void fireContactMoved( ContactGroup oldParentGroup, + ContactGroupYahooImpl newParentGroup, + ContactYahooImpl contact) + { + //bail out if no one's listening + if(parentOperationSet == null){ + logger.debug("No presence op. set available. Bailing out."); + return; + } + + //dispatch + parentOperationSet.fireSubscriptionMovedEvent( + contact, oldParentGroup, newParentGroup); + } + + /** + * Retrns a reference to the provider that created us. + * @return a reference to a ProtocolProviderServiceImpl instance. + */ + ProtocolProviderServiceYahooImpl getParentProvider() + { + return yahooProvider; + } + + /** + * Returns the ConntactGroup with the specified name or null if no such + * group was found. + * <p> + * @param name the name of the group we're looking for. + * @return a reference to the ContactGroupYahooImpl instance we're looking for + * or null if no such group was found. + */ + public ContactGroupYahooImpl findContactGroup(String name) + { + Iterator contactGroups = rootGroup.subgroups(); + + while(contactGroups.hasNext()) + { + ContactGroupYahooImpl contactGroup + = (ContactGroupYahooImpl) contactGroups.next(); + + if (contactGroup.getGroupName().equals(name)) + return contactGroup; + } + + return null; + } + + /** + * Returns the Contact with the specified id or null if + * no such id was found. + * + * @param id the id of the contact to find. + * @return the <tt>Contact</tt> carrying the specified + * <tt>screenName</tt> or <tt>null</tt> if no such contact exits. + */ + public ContactYahooImpl findContactById(String id) + { + Iterator contactGroups = rootGroup.subgroups(); + ContactYahooImpl result = null; + + while(contactGroups.hasNext()) + { + ContactGroupYahooImpl contactGroup + = (ContactGroupYahooImpl) contactGroups.next(); + + result = contactGroup.findContact(id); + + if (result != null) + return result; + + } + + Iterator rootContacts = rootGroup.contacts(); + while (rootContacts.hasNext()) + { + ContactYahooImpl item = (ContactYahooImpl) rootContacts.next(); + + if(item.getAddress().equals(id)) + return item; + } + + return null; + } + + /** + * Returns the ContactGroup containing the specified contact or null + * if no such group or contact exist. + * + * @param child the contact whose parent group we're looking for. + * @return the <tt>ContactGroup</tt> containing the specified + * <tt>contact</tt> or <tt>null</tt> if no such groupo or contact + * exist. + */ + public ContactGroup findContactGroup(ContactYahooImpl child) + { + Iterator contactGroups = rootGroup.subgroups(); + + while(contactGroups.hasNext()) + { + ContactGroupYahooImpl contactGroup + = (ContactGroupYahooImpl) contactGroups.next(); + + if( contactGroup.findContact(child.getAddress())!= null) + return contactGroup; + } + + Iterator contacts = rootGroup.contacts(); + + while(contacts.hasNext()) + { + ContactYahooImpl contact = (ContactYahooImpl) contacts.next(); + + if( contact.equals(child)) + return rootGroup; + } + + return null; + } + + /** + * Adds a new contact with the specified screenname to the list under a + * default location. + * @param id the id of the contact to add. + * @throws OperationFailedException + */ + public void addContact(String id) + throws OperationFailedException + { + ContactGroupYahooImpl parent = getFirstPersistentGroup(); + + if(parent == null) + { + // if there is no group create it + parent = createUnresolvedContactGroup(DEFAULT_GROUP_NAME); + } + + addContact(parent, id); + } + + /** + * Adds a new contact with the specified screenname to the list under the + * specified group. + * @param id the id of the contact to add. + * @param parent the group under which we want the new contact placed. + * @throws OperationFailedException if the contact already exist + */ + public void addContact(final ContactGroupYahooImpl parent, final String id) + throws OperationFailedException + { + logger.trace("Adding contact " + id + " to parent=" + parent); + + //if the contact is already in the contact list and is not volatile, + //then only broadcast an event + ContactYahooImpl existingContact = findContactById(id); + + if( existingContact != null + && existingContact.isPersistent() ) + { + logger.debug("Contact " + id + " already exists."); + throw new OperationFailedException( + "Contact " + id + " already exists.", + OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS); + } + + try + { + yahooSession.addFriend(id, parent.getGroupName()); + } + catch(IOException ex) + { + throw new OperationFailedException( + "Contact cannot be added " + id, + OperationFailedException.NETWORK_FAILURE); + } + } + + /** + * Creates a non persistent contact for the specified address. This would + * also create (if necessary) a group for volatile contacts that would not + * be added to the server stored contact list. This method would have no + * effect on the server stored contact list. + * @param id the address of the contact to create. + * @return the newly created volatile <tt>ContactImpl</tt> + */ + ContactYahooImpl createVolatileContact(String id) + { + VolatileContactYahooImpl newVolatileContact + = new VolatileContactYahooImpl(id, this); + + //Check whether a volatile group already exists and if not create + //one + ContactGroupYahooImpl theVolatileGroup = getNonPersistentGroup(); + + //if the parent group is null then add necessary create the group + if (theVolatileGroup == null) + { + theVolatileGroup = new VolatileContactGroupYahooImpl( + VOLATILE_GROUP_NAME, this); + + theVolatileGroup.addContact(newVolatileContact); + + this.rootGroup.addSubGroup(theVolatileGroup); + + fireGroupEvent(theVolatileGroup + , ServerStoredGroupEvent.GROUP_CREATED_EVENT); + } + else + { + theVolatileGroup.addContact(newVolatileContact); + + fireContactAdded(theVolatileGroup, newVolatileContact); + } + + return newVolatileContact; + } + + + /** + * Creates a non resolved contact for the specified address and inside the + * specified group. The newly created contact would be added to the local + * contact list as a standard contact but when an event is received from the + * server concerning this contact, then it will be reused and only its + * isResolved field would be updated instead of creating the whole contact + * again. + * + * @param parentGroup the group where the unersolved contact is to be + * created + * @param id the Address of the contact to create. + * @return the newly created unresolved <tt>ContactImpl</tt> + */ + ContactYahooImpl createUnresolvedContact(ContactGroup parentGroup, String id) + { + ContactYahooImpl newUnresolvedContact + = new ContactYahooImpl(id, this, false); + + if(parentGroup instanceof ContactGroupYahooImpl) + ((ContactGroupYahooImpl)parentGroup). + addContact(newUnresolvedContact); + + fireContactAdded(parentGroup, newUnresolvedContact); + + return newUnresolvedContact; + } + + /** + * Creates a non resolved contact group for the specified name. The newly + * created group would be added to the local contact list as any other group + * but when an event is received from the server concerning this group, then + * it will be reused and only its isResolved field would be updated instead + * of creating the whole group again. + * <p> + * @param groupName the name of the group to create. + * @return the newly created unresolved <tt>ContactGroupImpl</tt> + */ + ContactGroupYahooImpl createUnresolvedContactGroup(String groupName) + { + ContactGroupYahooImpl newUnresolvedGroup = + new ContactGroupYahooImpl(groupName, this); + + this.rootGroup.addSubGroup(newUnresolvedGroup); + + fireGroupEvent(newUnresolvedGroup + , ServerStoredGroupEvent.GROUP_CREATED_EVENT); + + return newUnresolvedGroup; + } + + /** + * Creates the specified group on the server stored contact list. + * @param groupName a String containing the name of the new group. + * @throws OperationFailedException with code CONTACT_GROUP_ALREADY_EXISTS + * if the group we're trying to create is already in our contact list. + */ + public void createGroup(String groupName) + throws OperationFailedException + { + logger.trace("Creating group: " + groupName); + + ContactGroupYahooImpl existingGroup = findContactGroup(groupName); + + if( existingGroup != null && existingGroup.isPersistent() ) + { + logger.debug("ContactGroup " + groupName + " already exists."); + throw new OperationFailedException( + "ContactGroup " + groupName + " already exists.", + OperationFailedException.CONTACT_GROUP_ALREADY_EXISTS); + } + + // create unresolved group if friend is added - group will be resolved + createUnresolvedContactGroup(groupName); + } + + /** + * Removes the specified group from the buddy list. + * @param groupToRemove the group that we'd like removed. + */ + public void removeGroup(ContactGroupYahooImpl groupToRemove) + { + // to remove group just remove all the contacts in it + + logger.trace("removing group " + groupToRemove); + + // if its not persistent group just remove it +// if(!groupToRemove.isPersistent()) +// { +// rootGroup.removeSubGroup(groupToRemove); +// fireGroupEvent(groupToRemove, +// ServerStoredGroupEvent.GROUP_REMOVED_EVENT); +// return; +// } + + Vector contacts = groupToRemove.getSourceGroup().getMembers(); + + if(contacts.size() == 0) + { + // the group is empty just remove it + rootGroup.removeSubGroup(groupToRemove); + fireGroupEvent(groupToRemove, + ServerStoredGroupEvent.GROUP_REMOVED_EVENT); + return; + } + + Iterator iter = contacts.iterator(); + while(iter.hasNext()) + { + YahooUser item = (YahooUser)iter.next(); + + try + { + yahooSession.removeFriend(item.getId(), groupToRemove.getGroupName()); + } + catch(IOException ex) + { + logger.info("Cannot Remove contact " + item.getId()); + } + } + } + + /** + * Removes a contact from the serverside list + * Event will come for successful operation + * @param contactToRemove ContactYahooImpl + */ + void removeContact(ContactYahooImpl contactToRemove) + { + logger.trace("Removing yahoo contact " + contactToRemove.getSourceContact()); + try + { + yahooSession.removeFriend( + contactToRemove.getSourceContact().getId(), + contactToRemove.getParentContactGroup().getGroupName()); + } + catch(IOException ex) + { + logger.info("Cannot Remove contact " + contactToRemove); + } + } + + /** + * Renames the specified group according to the specified new name.. + * @param groupToRename the group that we'd like removed. + * @param newName the new name of the group + */ + public void renameGroup(ContactGroupYahooImpl groupToRename, String newName) + { + // not working + /* + try + { + yahooSession.renameGroup(groupToRename.getGroupName(), newName); + } + catch(IOException ex) + { + logger.info("Cannot rename group " + groupToRename); + } + + fireGroupEvent(groupToRename, ServerStoredGroupEvent.GROUP_RENAMED_EVENT); + */ + } + + /** + * Moves the specified <tt>contact</tt> to the group indicated by + * <tt>newParent</tt>. + * @param contact the contact that we'd like moved under the new group. + * @param newParent the group where we'd like the parent placed. + */ + public void moveContact(ContactYahooImpl contact, + ContactGroupYahooImpl newParent) + { + try + { + contactListModListenerImpl. + waitForMove(contact.getSourceContact().getId(), + contact.getParentContactGroup().getGroupName()); + + yahooSession.addFriend( + contact.getSourceContact().getId(), + newParent.getGroupName()); + } + catch(IOException ex) + { + contactListModListenerImpl. + removeWaitForMove(contact.getSourceContact().getId()); + logger.error("Contact cannot be added " + ex.getMessage()); + } + } + + /** + * Returns the volatile group + * + * @return ContactGroupYahooImpl + */ + private ContactGroupYahooImpl getNonPersistentGroup() + { + for (int i = 0; i < getRootGroup().countSubgroups(); i++) + { + ContactGroupYahooImpl gr = + (ContactGroupYahooImpl)getRootGroup().getGroup(i); + + if(!gr.isPersistent()) + return gr; + } + + return null; + } + + /** + * Returns the first persistent group + * + * @return ContactGroupIcqImpl + */ + private ContactGroupYahooImpl getFirstPersistentGroup() + { + for (int i = 0; i < getRootGroup().countSubgroups(); i++) + { + ContactGroupYahooImpl gr = + (ContactGroupYahooImpl)getRootGroup().getGroup(i); + + if(gr.isPersistent()) + return gr; + } + + return null; + } + + /** + * Finds Group by provided its yahoo ID + * @param id String + * @return ContactGroupYahooImpl + */ + private ContactGroupYahooImpl findContactGroupByYahooId(String id) + { + Iterator contactGroups = rootGroup.subgroups(); + + while(contactGroups.hasNext()) + { + ContactGroupYahooImpl contactGroup + = (ContactGroupYahooImpl) contactGroups.next(); + + if (contactGroup.getSourceGroup().getName().equals(id)) + return contactGroup; + } + + return null; + } + + /** + * Make the parent persistent presence operation set dispatch a contact + * added event. + * @param parentGroup the group where the new contact was added + * @param contact the contact that was added + */ + void fireContactAdded( ContactGroup parentGroup, + ContactYahooImpl contact) + { + //bail out if no one's listening + if(parentOperationSet == null){ + logger.debug("No presence op. set available. Bailing out."); + return; + } + + //dispatch + parentOperationSet.fireSubscriptionEvent( + SubscriptionEvent.SUBSCRIPTION_CREATED, contact, parentGroup); + } + + /** + * Make the parent persistent presence operation set dispatch a contact + * resolved event. + * @param parentGroup the group that the resolved contact belongs to. + * @param contact the contact that was resolved + */ + void fireContactResolved( ContactGroup parentGroup, + ContactYahooImpl contact) + { + //bail out if no one's listening + if(parentOperationSet == null){ + logger.debug("No presence op. set available. Bailing out."); + return; + } + + //dispatch + parentOperationSet.fireSubscriptionEvent( + SubscriptionEvent.SUBSCRIPTION_RESOLVED, contact, parentGroup); + } + + /** + * Returns true if the contact list is initialized and + * ready for use, and false otherwise. + * + * @return true if the contact list is initialized and ready for use and false + * otherwise + */ + boolean isInitialized() + { + return isInitialized; + } + + /** + * When the protocol is online this method is used to fill or resolve + * the current contact list + */ + private void initList() + { + logger.trace("Start init list of " + yahooProvider.getAccountID().getUserID()); + + YahooGroup[] groups = yahooSession.getGroups(); + + for (int i = 0; i < groups.length; i++) + { + YahooGroup item = groups[i]; + + ContactGroupYahooImpl group = findContactGroup(item.getName()); + + if(group == null) + { + // create the group as it doesn't exist + group = + new ContactGroupYahooImpl(item, item.getMembers(), this, true); + + rootGroup.addSubGroup(group); + + //tell listeners about the added group + fireGroupEvent(group, ServerStoredGroupEvent.GROUP_CREATED_EVENT); + } + else + { + // the group exist so just resolved. The group will check and + // create or resolve its entries + group.setResolved(item); + + //fire an event saying that the group has been resolved + fireGroupEvent(group + , ServerStoredGroupEvent.GROUP_RESOLVED_EVENT); + + /** @todo if something to delete . delete it */ + } + + logger.trace("Init of group done! : " + group); + } + } + + /** + * @param name Name of the group to search + * @return The yahoo group with given name + */ + private YahooGroup findGroup(String name) + { + YahooGroup[] groups = yahooSession.getGroups(); + for (int i = 0; i < groups.length; i++) + { + YahooGroup elem = groups[i]; + if(elem.getName().equals(name)) + return elem; + } + return null; + } + + /** + * Imulates firing adding contact in group and moving contact to group. + * When moving contact it is first adding to the new group then + * it is removed from the old one. + */ + private class ContactListModListenerImpl + extends SessionAdapter + { + private Hashtable waitMove = new Hashtable(); + + public void waitForMove(String id, String oldParent) + { + waitMove.put(id, oldParent); + } + + public void removeWaitForMove(String id) + { + waitMove.remove(id); + } + + /** + * Successfully added a friend + * friend - YahooUser of friend + * group - name of group added to + */ + public void friendAddedReceived(SessionFriendEvent ev) + { + logger.trace("Receive event for adding a friend : " + ev); + + ContactGroupYahooImpl group = + findContactGroup(ev.getGroup()); + + if(group == null){ + logger.trace("Group not found!" + group); + return; + } + + // if group is note resolved resolve it + // this means newly created group + if(!group.isResolved()) + { + YahooGroup gr = findGroup(ev.getGroup()); + + if(gr != null) + group.setResolved(gr); + + // contact will be added when resolving the group + + return; + } + String contactID = ev.getFriend().getId(); + ContactYahooImpl contactToAdd = findContactById(contactID); + + if(contactToAdd == null) + { + contactToAdd = + new ContactYahooImpl(ev.getFriend(), + ServerStoredContactListYahooImpl.this, true, true); + } + else + if(!contactToAdd.isPersistent()) + { + // we must remove the volatile buddy as we will add + // the persistent one + ContactGroupYahooImpl parent = + (ContactGroupYahooImpl)contactToAdd.getParentContactGroup(); + parent.removeContact(contactToAdd); + + contactToAdd = + new ContactYahooImpl(ev.getFriend(), + ServerStoredContactListYahooImpl.this, true, true); + } + + Object isWaitingForMove = waitMove.get(contactID); + + if(isWaitingForMove != null && isWaitingForMove instanceof String) + { + // waits for move into group + // will remove it from old group and will wait for event remove + // from group, then will fire moved to group event + String oldParent = (String)isWaitingForMove; + + group.addContact(contactToAdd); + waitMove.put(contactID, group.getSourceGroup()); + try + { + yahooSession.removeFriend(contactID, oldParent); + } + catch(IOException ex) + { + logger.info("Cannot Remove(till moving) contact :" + + contactToAdd + " from group " + oldParent); + } + } + else + { + group.addContact(contactToAdd); + fireContactAdded(group, contactToAdd); + } + } + + /** + * Successfully removed a friend + * friend - YahooUser of friend + * group - name of group removed from + */ + public void friendRemovedReceived(SessionFriendEvent ev) + { + String contactID = ev.getFriend().getId(); + + // first check is this part of move action + Object waitForMoveObj = waitMove.get(contactID); + if(waitForMoveObj != null && waitForMoveObj instanceof YahooGroup) + { + // first get the group - oldParent + ContactGroupYahooImpl oldParent = findContactGroup(ev.getGroup()); + ContactYahooImpl contactToRemove = oldParent.findContact(contactID); + + oldParent.removeContact(contactToRemove); + waitMove.remove(contactID); + + ContactGroupYahooImpl newParent = + findContactGroup(((YahooGroup)waitForMoveObj).getName()); + + fireContactMoved(oldParent, newParent, contactToRemove); + return; + } + + ContactYahooImpl contactToRemove = findContactById(contactID); + + ContactGroupYahooImpl parentGroup = + (ContactGroupYahooImpl)contactToRemove.getParentContactGroup(); + parentGroup.removeContact(contactToRemove); + fireContactRemoved(parentGroup, contactToRemove); + + // check if the group is deleted. If the contact is the last one in + // the group. The group is also deleted + if(findGroup(ev.getGroup()) == null) + { + rootGroup.removeSubGroup(parentGroup); + fireGroupEvent(parentGroup, ServerStoredGroupEvent.GROUP_REMOVED_EVENT); + } + } + + /** + * Someone wants to add us to their friends list + * to - the target (us!) + * from - the user who wants to add us + * message - the request message text + */ + public void contactRequestReceived(SessionEvent ev) + { + logger.info("contactRequestReceived : " + ev); + + if(handler == null || ev.getFrom() == null) + return; + + ContactYahooImpl contact = findContactById(ev.getFrom()); + + if(contact == null) + contact = parentOperationSet.createVolatileContact(ev.getFrom()); + + AuthorizationRequest request = new AuthorizationRequest(); + request.setReason(ev.getMessage()); + + AuthorizationResponse resp = + handler.processAuthorisationRequest(request, contact); + + if (resp.getResponseCode() == AuthorizationResponse.REJECT) + { + try{ + yahooSession.rejectContact(ev, resp.getReason()); + }catch(IOException ex){ + logger.error("Cannot send reject : " + ex.getMessage()); + } + } + } + + /** + * Someone has rejected our attempts to add them to our friends list + * from - the user who rejected us + * message - rejection message text + */ + public void contactRejectionReceived(SessionEvent ev) + { + logger.info("contactRejectionReceived : " + ev); + + if(handler == null) + return; + + ContactYahooImpl contact = findContactById(ev.getFrom()); + + AuthorizationResponse resp = + new AuthorizationResponse(AuthorizationResponse.REJECT, ev.getMessage()); + handler.processAuthorizationResponse(resp, contact); + } + } + + /** + * Sets the yahoo session instance of the lib + * which comunicates with the server + * @param session YahooSession + */ + void setYahooSession(YahooSession session) + { + this.yahooSession = session; + session.addSessionListener(contactListModListenerImpl); + initList(); + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactGroupYahooImpl.java new file mode 100644 index 0000000..a5d0e5a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactGroupYahooImpl.java @@ -0,0 +1,79 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.util.*; + +/** + * The Yahoo implementation of the Volatile ContactGroup interface. + * + * @author Damian Minkov + */ +public class VolatileContactGroupYahooImpl + extends ContactGroupYahooImpl +{ + /** + * This contact group name + */ + private String contactGroupName = null; + + /** + * Creates an Yahoo group using the specified group name + * @param groupName String groupname + * @param ssclCallback a callback to the server stored contact list + * we're creating. + */ + VolatileContactGroupYahooImpl( + String groupName, + ServerStoredContactListYahooImpl ssclCallback) + { + super(groupName, ssclCallback); + this.contactGroupName = groupName; + } + + /** + * Returns the name of this group. + * @return a String containing the name of this group. + */ + public String getGroupName() + { + return contactGroupName; + } + + /** + * Returns a string representation of this group, in the form + * YahooGroup.GroupName[size]{ buddy1.toString(), buddy2.toString(), ...}. + * @return a String representation of the object. + */ + public String toString() + { + StringBuffer buff = new StringBuffer("VolatileYahooGroup."); + buff.append(getGroupName()); + buff.append(", childContacts="+countContacts()+":["); + + Iterator contacts = contacts(); + while (contacts.hasNext()) + { + ContactYahooImpl contact = (ContactYahooImpl) contacts.next(); + buff.append(contact.toString()); + if(contacts.hasNext()) + buff.append(", "); + } + return buff.append("]").toString(); + } + + /** + * Determines whether or not this contact group is being stored by the + * server. Non persistent contact groups exist for the sole purpose of + * containing non persistent contacts. + * @return true if the contact group is persistent and false otherwise. + */ + public boolean isPersistent() + { + return false; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactYahooImpl.java new file mode 100644 index 0000000..285f6e5 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactYahooImpl.java @@ -0,0 +1,81 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import net.java.sip.communicator.util.Logger; + +/** + * The Yahoo implementation for Volatile Contact + * @author Damian Minkov + */ +public class VolatileContactYahooImpl + extends ContactYahooImpl +{ + /** + * This contact id + */ + private String contactId = null; + /** + * Creates an Volatile YahooContactImpl with the specified id + * @param id String the user id/address + * @param ssclCallback a reference to the ServerStoredContactListImpl + * instance that created us. + */ + VolatileContactYahooImpl(String id, + ServerStoredContactListYahooImpl ssclCallback) + { + super(id, ssclCallback, false); + this.contactId = id; + } + + /** + * Returns the Yahoo Userid of this contact + * @return the Yahoo Userid of this contact + */ + public String getAddress() + { + return contactId; + } + + /** + * Returns a String that could be used by any user interacting modules for + * referring to this contact. An alias is not necessarily unique but is + * often more human readable than an address (or id). + * @return a String that can be used for referring to this contact when + * interacting with the user. + */ + public String getDisplayName() + { + return contactId; + } + + /** + * Returns a string representation of this contact, containing most of its + * representative details. + * + * @return a string representation of this contact. + */ + public String toString() + { + StringBuffer buff = new StringBuffer("VolatileYahooContact[ id="); + buff.append(getAddress()).append("]"); + + return buff.toString(); + } + + /** + * Determines whether or not this contact group is being stored by the + * server. Non persistent contact groups exist for the sole purpose of + * containing non persistent contacts. + * @return true if the contact group is persistent and false otherwise. + */ + public boolean isPersistent() + { + return false; + } + +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooAccountID.java b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooAccountID.java new file mode 100644 index 0000000..5662fd5 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooAccountID.java @@ -0,0 +1,30 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * The Yahoo implementation of a sip-communicator AccountID + * + * @author Damian Minkov + */ +public class YahooAccountID + extends AccountID +{ + /** + * Creates an account id from the specified id and account properties. + * @param id the id identifying this account + * @param accountProperties any other properties necessary for the account. + */ + YahooAccountID(String id, Map accountProperties ) + { + super(id, accountProperties, ProtocolNames.YAHOO, "yahoo.com"); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooActivator.java b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooActivator.java new file mode 100644 index 0000000..2874504 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooActivator.java @@ -0,0 +1,115 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.util.*; + +import org.osgi.framework.*; +import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * Loads the Yahoo provider factory and registers it with service in the OSGI + * bundle context. + * + * @author Damian Minkov + */ +public class YahooActivator + implements BundleActivator +{ + private ServiceRegistration yahooPpFactoryServReg = null; + private static BundleContext bundleContext = null; + private static ConfigurationService configurationService = null; + + private static ProtocolProviderFactoryYahooImpl yahooProviderFactory = null; + + /** + * Called when this bundle is started so the Framework can perform the + * bundle-specific activities necessary to start this bundle. + * + * @param context The execution context of the bundle being started. + * @throws Exception If this method throws an exception, this bundle is + * marked as stopped and the Framework will remove this bundle's + * listeners, unregister all services registered by this bundle, and + * release all services used by this bundle. + */ + public void start(BundleContext context) throws Exception + { + this.bundleContext = context; + Hashtable hashtable = new Hashtable(); + hashtable.put(ProtocolProviderFactory.PROTOCOL, ProtocolNames.YAHOO); + + yahooProviderFactory = new ProtocolProviderFactoryYahooImpl(); + + //load all yahoo providers + yahooProviderFactory.loadStoredAccounts(); + + //reg the yahoo account man. + yahooPpFactoryServReg = context.registerService( + ProtocolProviderFactory.class.getName(), + yahooProviderFactory, + hashtable); + } + + /** + * Returns a reference to a ConfigurationService implementation currently + * registered in the bundle context or null if no such implementation was + * found. + * + * @return ConfigurationService a currently valid implementation of the + * configuration service. + */ + public static ConfigurationService getConfigurationService() + { + if(configurationService == null) + { + ServiceReference confReference + = bundleContext.getServiceReference( + ConfigurationService.class.getName()); + configurationService + = (ConfigurationService) bundleContext.getService(confReference); + } + return configurationService; + } + + /** + * Returns a reference to the bundle context that we were started with. + * @return a reference to the BundleContext instance that we were started + * witn. + */ + public static BundleContext getBundleContext() + { + return bundleContext; + } + + /** + * Retrurns a reference to the protocol provider factory that we have + * registered. + * @return a reference to the <tt>ProtocolProviderFactoryYahooImpl</tt> + * instance that we have registered from this package. + */ + static ProtocolProviderFactoryYahooImpl getProtocolProviderFactory() + { + return yahooProviderFactory; + } + + /** + * Called when this bundle is stopped so the Framework can perform the + * bundle-specific activities necessary to stop the bundle. + * + * @param context The execution context of the bundle being stopped. + * @throws Exception If this method throws an exception, the bundle is + * still marked as stopped, and the Framework will remove the bundle's + * listeners, unregister all services registered by the bundle, and + * release all services used by the bundle. + */ + public void stop(BundleContext context) throws Exception + { + yahooProviderFactory.stop(); + yahooPpFactoryServReg.unregister(); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooSession.java b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooSession.java new file mode 100644 index 0000000..be04d71 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooSession.java @@ -0,0 +1,28 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.yahoo; + +import java.io.*; +import ymsg.network.*; + +/** + * Extends The Yahoo session to have access to some + * protected functionality + * Not working for now. + * + * @author Damian Minkov + */ +public class YahooSession + extends Session +{ + + public void renameGroup(String oldName, String newName) + throws IOException + { + transmitGroupRename(oldName, newName); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/yahoo.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/yahoo/yahoo.provider.manifest.mf new file mode 100644 index 0000000..523e8d4 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/yahoo.provider.manifest.mf @@ -0,0 +1,19 @@ +Bundle-Activator: net.java.sip.communicator.impl.protocol.yahoo.YahooActivator +Bundle-Name: Yahoo Protocol Provider Implementation +Bundle-Description: An Yahoo implementation of the Protocol Provider Service. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: org.osgi.framework, + javax.net.ssl, + javax.swing, + javax.xml.parsers, + javax.naming, + javax.naming.directory, + org.xml.sax, + sun.security.action, + net.java.sip.communicator.service.configuration, + net.java.sip.communicator.util, + net.java.sip.communicator.service.configuration.event, + net.java.sip.communicator.service.protocol, + net.java.sip.communicator.service.protocol.yahooconstants, + net.java.sip.communicator.service.protocol.event diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/FirstWizardPage.java b/src/net/java/sip/communicator/plugin/yahooaccregwizz/FirstWizardPage.java new file mode 100644 index 0000000..007a26f --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/FirstWizardPage.java @@ -0,0 +1,262 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.yahooaccregwizz; + +import java.awt.*; +import java.util.*; + +import javax.swing.*; +import javax.swing.event.*; + +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * The <tt>FirstWizardPage</tt> is the page, where user could enter the uin + * and the password of the account. + * + * @author Yana Stamcheva + * @author Damian Minkov + */ +public class FirstWizardPage extends JPanel + implements WizardPage, DocumentListener { + + public static final String FIRST_PAGE_IDENTIFIER = "FirstPageIdentifier"; + + private JPanel uinPassPanel = new JPanel(new BorderLayout(10, 10)); + + private JPanel labelsPanel = new JPanel(); + + private JPanel valuesPanel = new JPanel(); + + private JLabel uinLabel = new JLabel(Resources.getString("uin")); + + private JLabel passLabel = new JLabel(Resources.getString("password")); + + private JLabel existingAccountLabel + = new JLabel(Resources.getString("existingAccount")); + + private JPanel emptyPanel = new JPanel(); + + private JLabel uinExampleLabel = new JLabel("Ex: johnsmith@hotmail.com"); + + private JTextField uinField = new JTextField(); + + private JPasswordField passField = new JPasswordField(); + + private JCheckBox rememberPassBox = new JCheckBox( + Resources.getString("rememberPassword")); + + private JPanel mainPanel = new JPanel(); + + private Object nextPageIdentifier = WizardPage.SUMMARY_PAGE_IDENTIFIER; + + private YahooAccountRegistration registration; + + private WizardContainer wizardContainer; + + /** + * Creates an instance of <tt>FirstWizardPage</tt>. + * @param registration the <tt>YahooAccountRegistration</tt>, where + * all data through the wizard are stored + * @param wizardContainer the wizardContainer, where this page will + * be added + */ + public FirstWizardPage(YahooAccountRegistration registration, + WizardContainer wizardContainer) { + + super(new BorderLayout()); + + this.wizardContainer = wizardContainer; + + this.registration = registration; + + this.setPreferredSize(new Dimension(300, 150)); + + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + this.init(); + + this.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + this.labelsPanel.setLayout(new BoxLayout(labelsPanel, BoxLayout.Y_AXIS)); + + this.valuesPanel.setLayout(new BoxLayout(valuesPanel, BoxLayout.Y_AXIS)); + } + + /** + * Initializes all panels, buttons, etc. + */ + private void init() { + this.uinField.getDocument().addDocumentListener(this); + this.rememberPassBox.setSelected(true); + + this.existingAccountLabel.setForeground(Color.RED); + + this.uinExampleLabel.setForeground(Color.GRAY); + this.uinExampleLabel.setFont(uinExampleLabel.getFont().deriveFont(8)); + this.emptyPanel.setMaximumSize(new Dimension(40, 35)); + this.uinExampleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 8, 0)); + + labelsPanel.add(uinLabel); + labelsPanel.add(emptyPanel); + labelsPanel.add(passLabel); + + valuesPanel.add(uinField); + valuesPanel.add(uinExampleLabel); + valuesPanel.add(passField); + + uinPassPanel.add(labelsPanel, BorderLayout.WEST); + uinPassPanel.add(valuesPanel, BorderLayout.CENTER); + uinPassPanel.add(rememberPassBox, BorderLayout.SOUTH); + + uinPassPanel.setBorder(BorderFactory + .createTitledBorder(Resources.getString("uinAndPassword"))); + + mainPanel.add(uinPassPanel); + this.add(mainPanel, BorderLayout.NORTH); + } + + /** + * Implements the <code>WizardPage.getIdentifier</code> to return + * this page identifier. + */ + public Object getIdentifier() { + return FIRST_PAGE_IDENTIFIER; + } + + /** + * Implements the <code>WizardPage.getNextPageIdentifier</code> to return + * the next page identifier - the summary page. + */ + public Object getNextPageIdentifier() { + return nextPageIdentifier; + } + + /** + * Implements the <code>WizardPage.getBackPageIdentifier</code> to return + * the next back identifier - the default page. + */ + public Object getBackPageIdentifier() { + return WizardPage.DEFAULT_PAGE_IDENTIFIER; + } + + /** + * Implements the <code>WizardPage.getWizardForm</code> to return + * this panel. + */ + public Object getWizardForm() { + return this; + } + + /** + * Before this page is displayed enables or disables the "Next" wizard + * button according to whether the UIN field is empty. + */ + public void pageShowing() { + this.setNextButtonAccordingToUIN(); + } + + /** + * Saves the user input when the "Next" wizard buttons is clicked. + */ + public void pageNext() { + String uin = uinField.getText(); + + if(isExistingAccount(uin)) { + nextPageIdentifier = FIRST_PAGE_IDENTIFIER; + uinPassPanel.add(existingAccountLabel, BorderLayout.NORTH); + this.revalidate(); + } + else { + nextPageIdentifier = SUMMARY_PAGE_IDENTIFIER; + uinPassPanel.remove(existingAccountLabel); + + registration.setUin(uinField.getText()); + registration.setPassword(new String(passField.getPassword())); + registration.setRememberPassword(rememberPassBox.isSelected()); + } + } + + /** + * Enables or disables the "Next" wizard button according to whether the + * UIN field is empty. + */ + private void setNextButtonAccordingToUIN() { + if (uinField.getText() == null || uinField.getText().equals("")) { + wizardContainer.setNextFinishButtonEnabled(false); + } + else { + wizardContainer.setNextFinishButtonEnabled(true); + } + } + + /** + * Handles the <tt>DocumentEvent</tt> triggered when user types in the + * UIN field. Enables or disables the "Next" wizard button according to + * whether the UIN field is empty. + */ + public void insertUpdate(DocumentEvent e) { + this.setNextButtonAccordingToUIN(); + } + + /** + * Handles the <tt>DocumentEvent</tt> triggered when user deletes letters + * from the UIN field. Enables or disables the "Next" wizard button + * according to whether the UIN field is empty. + */ + public void removeUpdate(DocumentEvent e) { + this.setNextButtonAccordingToUIN(); + } + + public void changedUpdate(DocumentEvent e) { + } + + public void pageHiding() { + } + + public void pageShown() { + } + + public void pageBack() { + } + + /** + * Fills the UIN and Password fields in this panel with the data comming + * from the given protocolProvider. + * @param protocolProvider The <tt>ProtocolProviderService</tt> to load the + * data from. + */ + public void loadAccount(ProtocolProviderService protocolProvider) { + AccountID accountID = protocolProvider.getAccountID(); + String password = (String)accountID.getAccountProperties() + .get(ProtocolProviderFactory.PASSWORD); + + this.uinField.setText(accountID.getUserID()); + + if(password != null) { + this.passField.setText(password); + this.rememberPassBox.setSelected(true); + } + } + + private boolean isExistingAccount(String accountName) + { + ProtocolProviderFactory factory + = YahooAccRegWizzActivator.getYahooProtocolProviderFactory(); + + ArrayList registeredAccounts = factory.getRegisteredAccounts(); + + for(int i = 0; i < registeredAccounts.size(); i ++) { + AccountID accountID = (AccountID) registeredAccounts.get(i); + + if(accountName.equalsIgnoreCase(accountID.getUserID())) + return true; + } + return false; + } +} diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/Resources.java b/src/net/java/sip/communicator/plugin/yahooaccregwizz/Resources.java new file mode 100644 index 0000000..96001e6 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/Resources.java @@ -0,0 +1,81 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.yahooaccregwizz; + +import java.io.*; +import java.util.*; + +import net.java.sip.communicator.util.*; +/** + * The Messages class manages the access to the internationalization + * properties files. + * @author Yana Stamcheva + */ +public class Resources { + + private static Logger log = Logger.getLogger(Resources.class); + + private static final String BUNDLE_NAME + = "net.java.sip.communicator.plugin.yahooaccregwizz.resources"; + + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle + .getBundle(BUNDLE_NAME); + + public static ImageID YAHOO_LOGO = new ImageID("protocolIcon"); + + /** + * Returns an internationalized string corresponding to the given key. + * @param key The key of the string. + * @return An internationalized string corresponding to the given key. + */ + public static String getString(String key) { + try { + return RESOURCE_BUNDLE.getString(key); + + } catch (MissingResourceException e) { + + return '!' + key + '!'; + } + } + + /** + * Loads an image from a given image identifier. + * @param imageID The identifier of the image. + * @return The image for the given identifier. + */ + public static byte[] getImage(ImageID imageID) { + byte[] image = new byte[100000]; + + String path = Resources.getString(imageID.getId()); + try { + Resources.class.getClassLoader() + .getResourceAsStream(path).read(image); + + } catch (IOException e) { + log.error("Failed to load image:" + path, e); + } + + return image; + } + + /** + * Represents the Image Identifier. + */ + public static class ImageID { + private String id; + + private ImageID(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/SecondWizardPage.java b/src/net/java/sip/communicator/plugin/yahooaccregwizz/SecondWizardPage.java new file mode 100644 index 0000000..c5c537a --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/SecondWizardPage.java @@ -0,0 +1,48 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.yahooaccregwizz; + +import javax.swing.*; + +import net.java.sip.communicator.service.gui.*; + +public class SecondWizardPage extends JPanel + implements WizardPage { + + public static final String SECOND_PAGE_IDENTIFIER = "SecondPageIdentifier"; + + public Object getIdentifier() { + return SECOND_PAGE_IDENTIFIER; + } + + public Object getNextPageIdentifier() { + return FINISH_PAGE_IDENTIFIER; + } + + public Object getBackPageIdentifier() { + return FirstWizardPage.FIRST_PAGE_IDENTIFIER; + } + + public Object getWizardForm() { + return this; + } + + public void pageHiding() { + } + + public void pageShown() { + } + + public void pageShowing() { + } + + public void pageNext() { + } + + public void pageBack() { + } +} diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccRegWizzActivator.java b/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccRegWizzActivator.java new file mode 100644 index 0000000..fe35f38 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccRegWizzActivator.java @@ -0,0 +1,78 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.yahooaccregwizz; + +import org.osgi.framework.*; +import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * Registers the <tt>YahooAccountRegistrationWizard</tt> in the UI Service. + * + * @author Yana Stamcheva + */ +public class YahooAccRegWizzActivator implements BundleActivator { + + public static BundleContext bundleContext; + + private static Logger logger = Logger.getLogger( + YahooAccRegWizzActivator.class.getName()); + + private static ConfigurationService configService; + + /** + * Starts this bundle. + * @param bc BundleContext + * @throws Exception + */ + public void start(BundleContext bc) throws Exception { + + bundleContext = bc; + + ServiceReference uiServiceRef = bundleContext + .getServiceReference(UIService.class.getName()); + + UIService uiService + = (UIService) bundleContext.getService(uiServiceRef); + + AccountRegistrationWizardContainer wizardContainer + = uiService.getAccountRegWizardContainer(); + + YahooAccountRegistrationWizard yahooWizard + = new YahooAccountRegistrationWizard(wizardContainer); + + wizardContainer.addAccountRegistrationWizard(yahooWizard); + } + + public void stop(BundleContext bundleContext) throws Exception { + } + + /** + * Returns the <tt>ProtocolProviderFactory</tt> for the Yahoo protocol. + * @return the <tt>ProtocolProviderFactory</tt> for the Yahoo protocol + */ + public static ProtocolProviderFactory getYahooProtocolProviderFactory() { + + ServiceReference[] serRefs = null; + + String osgiFilter = "(" + + ProtocolProviderFactory.PROTOCOL + + "="+ProtocolNames.YAHOO+")"; + + try { + serRefs = bundleContext.getServiceReferences( + ProtocolProviderFactory.class.getName(), osgiFilter); + } + catch (InvalidSyntaxException ex){ + logger.error("YahooAccRegWizzActivator : " + ex); + } + + return (ProtocolProviderFactory) bundleContext.getService(serRefs[0]); + } +} diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccountRegistration.java b/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccountRegistration.java new file mode 100644 index 0000000..a93e3b5 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccountRegistration.java @@ -0,0 +1,72 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.yahooaccregwizz; + +/** + * The <tt>YahooAccountRegistration</tt> is used to store all user input data + * through the <tt>YahooAccountRegistrationWizard</tt>. + * + * @author Yana Stamcheva + */ +public class YahooAccountRegistration { + + private String uin; + + private String password; + + private boolean rememberPassword; + + /** + * Returns the password of the yahoo registration account. + * @return the password of the yahoo registration account. + */ + public String getPassword() { + return password; + } + + /** + * Sets the password of the yahoo registration account. + * @param password the password of the yahoo registration account. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Returns TRUE if password has to remembered, FALSE otherwise. + * @return TRUE if password has to remembered, FALSE otherwise + */ + public boolean isRememberPassword() { + return rememberPassword; + } + + /** + * Sets the rememberPassword value of this yahoo account registration. + * @param rememberPassword TRUE if password has to remembered, FALSE + * otherwise + */ + public void setRememberPassword(boolean rememberPassword) { + this.rememberPassword = rememberPassword; + } + + /** + * Returns the UIN of the yahoo registration account. + * @return the UIN of the yahoo registration account. + */ + public String getUin() { + return uin; + } + + /** + * Sets the UIN of the yahoo registration account. + * @param uin the UIN of the yahoo registration account. + */ + public void setUin(String uin) { + this.uin = uin; + } + +} diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccountRegistrationWizard.java b/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccountRegistrationWizard.java new file mode 100644 index 0000000..45bff02 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/YahooAccountRegistrationWizard.java @@ -0,0 +1,177 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.yahooaccregwizz; + +import java.util.*; + +import javax.swing.*; + +import org.osgi.framework.*; + +import net.java.sip.communicator.impl.gui.customcontrols.*; +import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * The <tt>YahooAccountRegistrationWizard</tt> is an implementation of the + * <tt>AccountRegistrationWizard</tt> for the Yahoo protocol. It should allow + * the user to create and configure a new Yahoo account. + * + * @author Yana Stamcheva + */ +public class YahooAccountRegistrationWizard implements AccountRegistrationWizard { + + private FirstWizardPage firstWizardPage; + + private YahooAccountRegistration registration + = new YahooAccountRegistration(); + + private WizardContainer wizardContainer; + + private ProtocolProviderService protocolProvider; + + private String propertiesPackage = "net.java.sip.communicator.plugin.yahooaccregwizz"; + + private boolean isModification; + + /** + * Creates an instance of <tt>YahooAccountRegistrationWizard</tt>. + * @param wizardContainer the wizard container, where this wizard + * is added + */ + public YahooAccountRegistrationWizard(WizardContainer wizardContainer) { + this.wizardContainer = wizardContainer; + } + + /** + * Implements the <code>AccountRegistrationWizard.getIcon</code> method. + * Returns the icon to be used for this wizard. + * @return byte[] + */ + public byte[] getIcon() { + return Resources.getImage(Resources.YAHOO_LOGO); + } + + /** + * Implements the <code>AccountRegistrationWizard.getProtocolName</code> + * method. Returns the protocol name for this wizard. + * @return String + */ + public String getProtocolName() { + return Resources.getString("protocolName"); + } + + /** + * Implements the <code>AccountRegistrationWizard.getProtocolDescription + * </code> method. Returns the description of the protocol for this wizard. + * @return String + */ + public String getProtocolDescription() { + return Resources.getString("protocolDescription"); + } + + /** + * Returns the set of pages contained in this wizard. + * @return Iterator + */ + public Iterator getPages() { + ArrayList pages = new ArrayList(); + firstWizardPage = new FirstWizardPage(registration, wizardContainer); + + pages.add(firstWizardPage); + + return pages.iterator(); + } + + /** + * Returns the set of data that user has entered through this wizard. + * @return Iterator + */ + public Iterator getSummary() { + Hashtable summaryTable = new Hashtable(); + + summaryTable.put("UIN", registration.getUin()); + summaryTable.put("Remember password", + new Boolean(registration.isRememberPassword())); + + return summaryTable.entrySet().iterator(); + } + + /** + * Installs the account created through this wizard. + * @return ProtocolProviderService + */ + public ProtocolProviderService finish() { + firstWizardPage = null; + ProtocolProviderFactory factory + = YahooAccRegWizzActivator.getYahooProtocolProviderFactory(); + + return this.installAccount(factory, + registration.getUin(), registration.getPassword()); + } + + /** + * Creates an account for the given user and password. + * @param providerFactory the ProtocolProviderFactory which will create + * the account + * @param user the user identifier + * @param passwd the password + * @return the <tt>ProtocolProviderService</tt> for the new account. + */ + public ProtocolProviderService installAccount( + ProtocolProviderFactory providerFactory, + String user, + String passwd) { + + Hashtable accountProperties = new Hashtable(); + + if(registration.isRememberPassword()) { + accountProperties.put(ProtocolProviderFactory.PASSWORD, passwd); + } + + if(isModification) { + providerFactory.uninstallAccount(protocolProvider.getAccountID()); + this.protocolProvider = null; + } + + try { + AccountID accountID = providerFactory.installAccount( + user, accountProperties); + + ServiceReference serRef = providerFactory + .getProviderForAccount(accountID); + + protocolProvider + = (ProtocolProviderService) YahooAccRegWizzActivator.bundleContext + .getService(serRef); + } + catch (IllegalArgumentException e) { + new ErrorDialog(null, e.getMessage()).showDialog(); + } + catch (IllegalStateException e) { + new ErrorDialog(null, e.getMessage()).showDialog(); + } + + return protocolProvider; + } + + /** + * Fills the UIN and Password fields in this panel with the data comming + * from the given protocolProvider. + * @param protocolProvider The <tt>ProtocolProviderService</tt> to load the + * data from. + */ + public void loadAccount(ProtocolProviderService protocolProvider) { + + this.protocolProvider = protocolProvider; + + this.firstWizardPage.loadAccount(protocolProvider); + + this.isModification = true; + } +} diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/resources.properties b/src/net/java/sip/communicator/plugin/yahooaccregwizz/resources.properties new file mode 100644 index 0000000..149fdde --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/resources.properties @@ -0,0 +1,9 @@ +protocolName=YAHOO +protocolDescription=A protocol to connect and chat on the Yahoo Service. +uin=Username: +password=Password: +rememberPassword=Remember password +uinAndPassword=ID and Password +existingAccount=* The account you entered is already installed. + +protocolIcon=net/java/sip/communicator/plugin/yahooaccregwizz/resources/yahoo.png diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/resources/yahoo.png b/src/net/java/sip/communicator/plugin/yahooaccregwizz/resources/yahoo.png Binary files differnew file mode 100644 index 0000000..feaf973 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/resources/yahoo.png diff --git a/src/net/java/sip/communicator/plugin/yahooaccregwizz/yahooaccregwizz.manifest.mf b/src/net/java/sip/communicator/plugin/yahooaccregwizz/yahooaccregwizz.manifest.mf new file mode 100644 index 0000000..787f3e2 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/yahooaccregwizz/yahooaccregwizz.manifest.mf @@ -0,0 +1,30 @@ +Bundle-Activator: net.java.sip.communicator.plugin.yahooaccregwizz.YahooAccRegWizzActivator +Bundle-Name: Yahoo account registration wizard +Bundle-Description: Yahoo account registration wizard. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: org.osgi.framework, + net.java.sip.communicator.util, + net.java.sip.communicator.service.configuration, + net.java.sip.communicator.service.configuration.event, + net.java.sip.communicator.service.protocol, + net.java.sip.communicator.service.protocol.icqconstants, + net.java.sip.communicator.service.protocol.event, + net.java.sip.communicator.service.contactlist, + net.java.sip.communicator.service.contactlist.event, + net.java.sip.communicator.service.gui, + net.java.sip.communicator.service.gui.event, + javax.swing, + javax.swing.event, + javax.swing.table, + javax.swing.text, + javax.swing.text.html, + javax.accessibility, + javax.swing.plaf, + javax.swing.plaf.metal, + javax.swing.plaf.basic, + javax.imageio, + javax.swing.filechooser, + javax.swing.tree, + javax.swing.undo, + javax.swing.border diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolNames.java b/src/net/java/sip/communicator/service/protocol/ProtocolNames.java index 1c76f69..71bddd8 100644 --- a/src/net/java/sip/communicator/service/protocol/ProtocolNames.java +++ b/src/net/java/sip/communicator/service/protocol/ProtocolNames.java @@ -54,7 +54,7 @@ public interface ProtocolNames /** * The Yahoo! messenger protcool. */ - public static final String YAHOO = "Yahoo!"; + public static final String YAHOO = "Yahoo"; /** * The Skype protcool. diff --git a/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf b/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf index 685c671..0ba2dfe 100644 --- a/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf +++ b/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf @@ -10,4 +10,5 @@ Export-Package: net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.icqconstants, net.java.sip.communicator.service.protocol.jabberconstants, net.java.sip.communicator.service.protocol.msnconstants, + net.java.sip.communicator.service.protocol.yahooconstants, net.java.sip.communicator.service.protocol.event diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountInstallation.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountInstallation.java new file mode 100644 index 0000000..68f6efd --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountInstallation.java @@ -0,0 +1,217 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import java.util.*; + +import org.osgi.framework.*; +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +public class TestAccountInstallation + extends TestCase +{ + private static final Logger logger = + Logger.getLogger(TestAccountInstallation.class); + + /** + * Creates the test with the specified method name. + * @param name the name of the method to execute. + */ + public TestAccountInstallation(String name) + { + super(name); + } + + /** + * JUnit setup method. + * @throws Exception in case anything goes wrong. + */ + protected void setUp() throws Exception + { + super.setUp(); + } + + /** + * JUnit teardown method. + * @throws Exception in case anything goes wrong. + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + /** + * Installs an account and verifies whether the installation has gone well. + */ + public void testInstallAccount() + { + // first obtain a reference to the provider factory + ServiceReference[] serRefs = null; + String osgiFilter = "(" + ProtocolProviderFactory.PROTOCOL + + "="+ProtocolNames.YAHOO+")"; + try{ + serRefs = YahooSlickFixture.bc.getServiceReferences( + ProtocolProviderFactory.class.getName(), osgiFilter); + } + catch (InvalidSyntaxException ex) + { + //this really shouldhn't occur as the filter expression is static. + fail(osgiFilter + " is not a valid osgi filter"); + } + + assertTrue( + "Failed to find a provider factory service for protocol Yahoo", + serRefs != null && serRefs.length > 0); + + //Keep the reference for later usage. + ProtocolProviderFactory yahooProviderFactory = (ProtocolProviderFactory) + YahooSlickFixture.bc.getService(serRefs[0]); + + //make sure the account is empty + assertTrue("There was an account registered with the account mananger " + +"before we've installed any", + yahooProviderFactory.getRegisteredAccounts().size() == 0); + + + //Prepare the properties of the first yahoo account. + + Hashtable yahooAccount1Properties = getAccountProperties( + YahooProtocolProviderServiceLick.ACCOUNT_1_PREFIX); + Hashtable yahooAccount2Properties = getAccountProperties( + YahooProtocolProviderServiceLick.ACCOUNT_2_PREFIX); + + //try to install an account with a null account id + try{ + yahooProviderFactory.installAccount( + null, yahooAccount1Properties); + fail("installing an account with a null account id must result " + +"in a NullPointerException"); + }catch(NullPointerException exc) + { + //that's what had to happen + } + + //now really install the accounts + yahooProviderFactory.installAccount( + (String)yahooAccount1Properties.get(ProtocolProviderFactory.USER_ID) + , yahooAccount1Properties); + yahooProviderFactory.installAccount( + (String)yahooAccount2Properties.get(ProtocolProviderFactory.USER_ID) + , yahooAccount2Properties); + + + //try to install one of the accounts one more time and verify that an + //excepion is thrown. + try{ + yahooProviderFactory.installAccount( + (String)yahooAccount1Properties.get(ProtocolProviderFactory.USER_ID) + , yahooAccount1Properties); + + fail("An IllegalStateException must be thrown when trying to "+ + "install a duplicate account"); + + }catch(IllegalStateException exc) + { + //that's what supposed to happen. + } + + //Verify that the provider factory is aware of our installation + assertTrue( + "The newly installed account was not in the acc man's " + +"registered accounts!", + yahooProviderFactory.getRegisteredAccounts().size() == 2); + + //Verify protocol providers corresponding to the new account have + //been properly registered with the osgi framework. + + osgiFilter = + "(&("+ProtocolProviderFactory.PROTOCOL +"="+ProtocolNames.YAHOO+")" + +"(" + ProtocolProviderFactory.USER_ID + + "=" + (String)yahooAccount1Properties.get( + ProtocolProviderFactory.USER_ID) + + "))"; + + try + { + serRefs = YahooSlickFixture.bc.getServiceReferences( + ProtocolProviderService.class.getName(), + osgiFilter); + } + catch (InvalidSyntaxException ex) + { + //this really shouldhn't occur as the filter expression is static. + fail(osgiFilter + "is not a valid osgi filter"); + } + + assertTrue("An protocol provider was apparently not installed as " + + "requested." + , serRefs != null && serRefs.length > 0); + + Object yahooProtocolProvider + = YahooSlickFixture.bc.getService(serRefs[0]); + + assertTrue("The installed protocol provider does not implement " + + "the protocol provider service." + ,yahooProtocolProvider instanceof ProtocolProviderService); + } + + /** + * Returns all properties necessary for the intialization of the account + * with <tt>accountPrefix</tt>. + * @param accountPrefix the prefix contained by all property names for the + * the account we'd like to initialized + * @return a Hashtable that can be used when creating the account in a + * protocol provider factory. + */ + private Hashtable getAccountProperties(String accountPrefix) + { + Hashtable table = new Hashtable(); + + String userID = System.getProperty( + accountPrefix + ProtocolProviderFactory.USER_ID, null); + + assertNotNull( + "The system property named " + + accountPrefix + ProtocolProviderFactory.USER_ID + +" has to tontain a valid yahoo address that could be used during " + +"SIP Communicator's tests." + , userID); + + table.put(ProtocolProviderFactory.USER_ID, userID); + + String passwd = System.getProperty( + accountPrefix + ProtocolProviderFactory.PASSWORD, null ); + + assertNotNull( + "The system property named " + + accountPrefix + ProtocolProviderFactory.PASSWORD + +" has to contain the password corresponding to the account " + + "specified in " + + accountPrefix + ProtocolProviderFactory.USER_ID + , passwd); + + table.put(ProtocolProviderFactory.PASSWORD, passwd); + + String serverAddress = System.getProperty( + accountPrefix + ProtocolProviderFactory.SERVER_ADDRESS, null); + + // optional + if(serverAddress != null) + table.put(ProtocolProviderFactory.SERVER_ADDRESS, serverAddress); + + String serverPort = System.getProperty( + accountPrefix + ProtocolProviderFactory.SERVER_PORT, null); + + // optional + if(serverPort != null) + table.put(ProtocolProviderFactory.SERVER_PORT, serverPort); + + return table; + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountUninstallation.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountUninstallation.java new file mode 100644 index 0000000..cd2a15b --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountUninstallation.java @@ -0,0 +1,270 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import org.osgi.framework.*; +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + + +/** + * Tests whether accaounts are uninstalled properly. It is important that + * tests from this class be called last since they will install the accounts + * that have been used to test the implementations. Apart from uninstallation + * tests the class also contains tests that remove and reinstall the protocol + * provider bundle in order to verify that accounts are persistent. + * + * @author Emil Ivov + */ +public class TestAccountUninstallation + extends TestCase +{ + private static final Logger logger = + Logger.getLogger(TestAccountUninstallation.class); + + private YahooSlickFixture fixture = new YahooSlickFixture(); + + /** + * Constructs a test instance + * @param name The name of the test. + */ + public TestAccountUninstallation(String name) + { + super(name); + } + + /** + * JUnit setup method. + * @throws Exception in case anything goes wrong. + */ + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + } + + /** + * JUnit teardown method. + * @throws Exception in case anything goes wrong. + */ + protected void tearDown() throws Exception + { + fixture.tearDown(); + super.tearDown(); + } + + /** + * Returns a suite containing tests in this class in the order that we'd + * like them executed. + * @return a Test suite containing tests in this class in the order that + * we'd like them executed. + */ + public static Test suite() + { + TestSuite suite = new TestSuite(); + + suite.addTest( + new TestAccountUninstallation("testInstallationPersistency")); + suite.addTest( + new TestAccountUninstallation("testUninstallAccount")); + + return suite; + } + + /** + * Stops and removes the tested bundle, verifies that it has unregistered + * its provider, then reloads and restarts the bundle and verifies that + * the protocol provider is reRegistered in the bundle context. + * + * @throws java.lang.Exception if an exception occurs during testing. + */ + public void testInstallationPersistency() throws Exception + { + Bundle providerBundle + = fixture.findProtocolProviderBundle(fixture.provider1); + + //set the global providerBundle reference that we will be using + //in the last series of tests (Account uninstallation persistency) + YahooSlickFixture.providerBundle = providerBundle; + + assertNotNull("Couldn't find a bundle for the tested provider" + , providerBundle); + + providerBundle.stop(); + + assertTrue("Couldn't stop the protocol provider bundle. State was " + + providerBundle.getState() + , Bundle.ACTIVE != providerBundle.getState() + && Bundle.STOPPING != providerBundle.getState()); + + providerBundle.uninstall(); + + assertEquals("Couldn't stop the protocol provider bundle." + , Bundle.UNINSTALLED, providerBundle.getState()); + + //verify that the provider is no longer available + ServiceReference[] yahooProviderRefs = null; + try + { + yahooProviderRefs = fixture.bc.getServiceReferences( + ProtocolProviderService.class.getName(), + "(&" + + "(" + ProtocolProviderFactory.PROTOCOL + + "=" +ProtocolNames.YAHOO + ")" + + "(" + ProtocolProviderFactory.USER_ID + + "="+ fixture.userID1 + ")" + + ")"); + } + catch (InvalidSyntaxException ex) + { + fail("We apparently got our filter wrong: " + ex.getMessage()); + } + + //make sure we didn't see a service + assertTrue("A Protocol Provider Service was still regged as an osgi service " + +"for yahoo URI:" + fixture.userID1 + + "After it was explicitly uninstalled" + ,yahooProviderRefs == null || yahooProviderRefs.length == 0); + + //verify that the provider factory knows that we have uninstalled the + //provider. + assertTrue( + "The yahoo provider factory kept a reference to the provider we just " + +"uninstalled (uri="+fixture.userID1+")", + fixture.providerFactory.getRegisteredAccounts().isEmpty() + && fixture.providerFactory.getProviderForAccount( + fixture.provider1.getAccountID()) + == null); + + //Now reinstall the bundle + providerBundle = fixture.bc.installBundle(providerBundle.getLocation()); + + //set the global providerBundle reference that we will be using + //in the last series of tests (Account uninstallation persistency) + YahooSlickFixture.providerBundle = providerBundle; + + assertEquals("Couldn't re-install protocol provider bundle." + , Bundle.INSTALLED, providerBundle.getState()); + + providerBundle.start(); + assertEquals("Couldn't re-start protocol provider bundle." + , Bundle.ACTIVE, providerBundle.getState()); + + //Make sure that the provider is there again. + //verify that the provider is no longer available + try + { + yahooProviderRefs = fixture.bc.getServiceReferences( + ProtocolProviderService.class.getName(), + "(&" + + "(" + ProtocolProviderFactory.PROTOCOL + + "=" +ProtocolNames.YAHOO + ")" + + "(" + ProtocolProviderFactory.USER_ID + + "="+ fixture.userID1 + ")" + + ")"); + } + catch (InvalidSyntaxException ex) + { + fail("We apparently got our filter wrong " + ex.getMessage()); + } + + //make sure we didn't see a service + assertTrue("A Protocol Provider Service was not restored after being" + +"reinstalled. yahoo URI:" + fixture.userID1 + ,yahooProviderRefs != null && yahooProviderRefs.length > 0); + + ServiceReference[] yahooFactoryRefs = null; + try + { + yahooFactoryRefs = fixture.bc.getServiceReferences( + ProtocolProviderFactory.class.getName(), + "(" + ProtocolProviderFactory.PROTOCOL + + "=" +ProtocolNames.YAHOO + ")"); + } + catch (InvalidSyntaxException ex) + { + fail("We apparently got our filter wrong " + ex.getMessage()); + } + + //we're the ones who've reinstalled the factory so it's our + //responsibility to update the fixture. + fixture.providerFactory + = (ProtocolProviderFactory)fixture.bc.getService(yahooFactoryRefs[0]); + fixture.provider1 + = (ProtocolProviderService)fixture.bc.getService(yahooProviderRefs[0]); + + + //verify that the provider is also restored in the provider factory + //itself + assertTrue( + "The yahoo provider did not restore its own reference to the provider " + +"that we just reinstalled (URI="+fixture.userID1+")", + !fixture.providerFactory.getRegisteredAccounts().isEmpty() + && fixture.providerFactory.getProviderForAccount( + fixture.provider1.getAccountID()) + != null); + + } + + /** + * Uinstalls our test account and makes sure it really has been removed. + * + */ + public void testUninstallAccount() + { + assertFalse("No installed accounts found", + fixture.providerFactory.getRegisteredAccounts().isEmpty()); + + assertNotNull( + "Found no provider corresponding to URI " + fixture.userID1 + ,fixture.providerFactory.getProviderForAccount( + fixture.provider1.getAccountID())); + + assertTrue( + "Failed to remove a provider corresponding to URI " + + fixture.userID1 + ,fixture.providerFactory.uninstallAccount( + fixture.provider1.getAccountID())); + assertTrue( + "Failed to remove a provider corresponding to URI " + + fixture.userID1 + ,fixture.providerFactory.uninstallAccount( + fixture.provider2.getAccountID())); + + //make sure no providers have remained installed. + ServiceReference[] yahooProviderRefs = null; + try + { + yahooProviderRefs = fixture.bc.getServiceReferences( + ProtocolProviderService.class.getName(), + "(" + ProtocolProviderFactory.PROTOCOL + + "=" +ProtocolNames.YAHOO + ")"); + } + catch (InvalidSyntaxException ex) + { + fail("We apparently got our filter wrong " + ex.getMessage()); + } + + //make sure we didn't see a service + assertTrue("A Protocol Provider Service was still regged as an osgi " + + "service for yahoo URI:" + fixture.userID1 + + "After it was explicitly uninstalled" + ,yahooProviderRefs == null || yahooProviderRefs.length == 0); + + //verify that the provider factory knows that we have uninstalled the + //provider. + assertTrue( + "The yahoo provider factory kept a reference to the provider we just " + +"uninstalled (uri="+fixture.userID1+")", + fixture.providerFactory.getRegisteredAccounts().isEmpty() + && fixture.providerFactory.getProviderForAccount( + fixture.provider1.getAccountID()) + == null); + + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountUninstallationPersistence.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountUninstallationPersistence.java new file mode 100644 index 0000000..f621abc --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestAccountUninstallationPersistence.java @@ -0,0 +1,100 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import org.osgi.framework.*; +import junit.framework.*; +import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * Contains tests verifying persistence of account uninstallation. In other + * words we try to make sure that once uninstalled an account remains + * uninstalled. + * + * @author Emil Ivov + */ +public class TestAccountUninstallationPersistence + extends TestCase +{ + /** + * Creates a new test instance wrapper around the test with the specified + * name. + * @param testName the name of the test that we will be executing. + */ + public TestAccountUninstallationPersistence(String testName) + { + super(testName); + } + + /** + * Retrieves a reference to the yahoo bundle, stops it and uninstalls it and + * then reinstalls it in order to make sure that accounts are not reloaded + * once removed. + * + * @throws java.lang.Exception if something goes wrong while manipulating + * the bundles. + */ + public void testAccountUninstallationPersistence() + throws Exception + { + Bundle providerBundle = YahooSlickFixture.providerBundle; + + providerBundle.stop(); + + assertTrue("Couldn't stop the protocol provider bundle. State was " + + providerBundle.getState() + , Bundle.ACTIVE != providerBundle.getState() + && Bundle.STOPPING != providerBundle.getState()); + + providerBundle.uninstall(); + + assertEquals("Couldn't stop the protocol provider bundle." + , Bundle.UNINSTALLED, providerBundle.getState()); + + //Now reinstall the bundle and restart the provider + providerBundle + = YahooSlickFixture.bc.installBundle(providerBundle.getLocation()); + + assertEquals("Couldn't re-install protocol provider bundle." + , Bundle.INSTALLED, providerBundle.getState()); + + providerBundle.start(); + assertEquals("Couldn't re-start protocol provider bundle." + , Bundle.ACTIVE, providerBundle.getState()); + + + //verify that the provider is not reinstalled + ServiceReference[] yahooProviderRefs = null; + try + { + yahooProviderRefs = YahooSlickFixture.bc.getServiceReferences( + ProtocolProviderService.class.getName(), + "(" + ProtocolProviderFactory.PROTOCOL + + "=" +ProtocolNames.YAHOO + ")"); + } + catch (InvalidSyntaxException ex) + { + fail("We apparently got our filter wrong " + ex.getMessage()); + } + + //make sure we didn't retrieve a service + assertTrue("A yahoo Protocol Provider Service was still regged as an " + +"osgi service after it was explicitly uninstalled" + ,yahooProviderRefs == null || yahooProviderRefs.length == 0); + + //and a nasty hack at the end - delete the configuration file so that + //we get a fresh start on next run. + ServiceReference confReference + = YahooSlickFixture.bc.getServiceReference( + ConfigurationService.class.getName()); + ConfigurationService configurationService + = (ConfigurationService) YahooSlickFixture.bc.getService(confReference); + + configurationService.purgeStoredConfiguration(); + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetBasicInstantMessaging.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetBasicInstantMessaging.java new file mode 100644 index 0000000..917ac4b --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetBasicInstantMessaging.java @@ -0,0 +1,510 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import java.net.*; +import java.util.*; + +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.Message; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Performs testing of the basic instant messaging operation set. Tests include + * going over basic functionality such as sending a message from the tested + * implementation and asserting reception by the tester agent and vice versa. + * @author Emil Ivov + */ +public class TestOperationSetBasicInstantMessaging + extends TestCase +{ + private static final Logger logger = + Logger.getLogger(TestOperationSetBasicInstantMessaging.class); + + private YahooSlickFixture fixture = new YahooSlickFixture(); + + private OperationSetBasicInstantMessaging opSetBasicIM1 = null; + private OperationSetBasicInstantMessaging opSetBasicIM2 = null; + + private OperationSetPresence opSetPresence1 = null; + private OperationSetPresence opSetPresence2 = null; + + public TestOperationSetBasicInstantMessaging(String name) + { + super(name); + } + + /** + * Get a reference to the basic IM operation set. + * @throws Exception if this is not a good day. + */ + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + + Map supportedOperationSets1 = + fixture.provider1.getSupportedOperationSets(); + + if ( supportedOperationSets1 == null + || supportedOperationSets1.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this implementation. "); + + //get the operation set presence here. + opSetBasicIM1 = + (OperationSetBasicInstantMessaging)supportedOperationSets1.get( + OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM1 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + //we also need the presence op set in order to retrieve contacts. + opSetPresence1 = + (OperationSetPresence)supportedOperationSets1.get( + OperationSetPresence.class.getName()); + + //if the op set is null show that we're not happy. + if (opSetPresence1 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + + Map supportedOperationSets2 = + fixture.provider2.getSupportedOperationSets(); + + if ( supportedOperationSets2 == null + || supportedOperationSets2.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this implementation. "); + + //get the operation set presence here. + opSetBasicIM2 = + (OperationSetBasicInstantMessaging)supportedOperationSets2.get( + OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM2 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + opSetPresence2 = + (OperationSetPresence) supportedOperationSets2.get( + OperationSetPresence.class.getName()); + + //if the op set is null show that we're not happy. + if (opSetPresence2 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + + } + + protected void tearDown() throws Exception + { + super.tearDown(); + + fixture.tearDown(); + } + + /** + * Creates a test suite containing tests of this class in a specific order. + * We'll first execute tests beginning with the "test" prefix and then go to + * ordered tests.We first execture tests for receiving messagese, so that + * a volatile contact is created for the sender. we'll then be able to + * retrieve this volatile contact and send them a message on our turn. + * We need to do things this way as the contact corresponding to the tester + * agent has been removed in the previous test and we no longer have it + * in our contact list. + * + * @return Test a testsuite containing all tests to execute. + */ + public static Test suite() + { + TestSuite suite = new TestSuite(); + + suite.addTest(new TestOperationSetBasicInstantMessaging( + "prepareContactList")); + + suite.addTestSuite(TestOperationSetBasicInstantMessaging.class); + + //the following 2 need to be run in the specified order. + suite.addTest(new TestOperationSetBasicInstantMessaging( + "firstTestReceiveMessage")); + suite.addTest(new TestOperationSetBasicInstantMessaging( + "thenTestSendMessage")); + + return suite; + } + + /** + * Create the list to be sure that contacts exchanging messages + * exists in each other lists + * @throws Exception + */ + public void prepareContactList() + throws Exception + { + fixture.clearProvidersLists(); + + Object o = new Object(); + synchronized(o) + { + o.wait(2000); + } + + try + { + opSetPresence1.subscribe(fixture.userID2); + } + catch (OperationFailedException ex) + { + // the contact already exist its OK + } + + try + { + opSetPresence2.subscribe(fixture.userID1); + } + catch (OperationFailedException ex1) + { + // the contact already exist its OK + } + + synchronized(o) + { + o.wait(2000); + } + } + + /** + * Send an instant message from the tested operation set and assert + * reception by the tester agent. + */ + public void firstTestReceiveMessage() + { + String body = "This is an IM coming from the tester agent" + + " on " + new Date().toString(); + + ImEventCollector evtCollector = new ImEventCollector(); + + //add a msg listener and register to the op set and send an instant + //msg from the tester agent. + opSetBasicIM1.addMessageListener(evtCollector); + + Contact testerAgentContact + = opSetPresence2.findContactByID(fixture.userID1); + + logger.debug("Will send message " + body + " to: " + testerAgentContact); + + opSetBasicIM2.sendInstantMessage(testerAgentContact, + opSetBasicIM2.createMessage(body)); + + evtCollector.waitForEvent(10000); + + opSetBasicIM1.removeMessageListener(evtCollector); + + //assert reception of a message event + assertTrue( "No events delivered upon a received message" + , evtCollector.collectedEvents.size() > 0); + + //assert event instance of Message Received Evt + assertTrue( "Received evt was not an instance of " + + MessageReceivedEvent.class.getName() + , evtCollector.collectedEvents.get(0) + instanceof MessageReceivedEvent); + + //assert source contact == testAgent.uin + MessageReceivedEvent evt + = (MessageReceivedEvent)evtCollector.collectedEvents.get(0); + assertEquals("message sender " + , evt.getSourceContact().getAddress() + , fixture.userID2); + + //assert messageBody == body + assertEquals("message body", body, evt.getSourceMessage().getContent()); + } + + /** + * Send an instant message from the tester agent and assert reception by + * the tested implementation + */ + public void thenTestSendMessage() + { + String body = "This is an IM coming from the tested implementation" + + " on " + new Date().toString(); + + //create the message + net.java.sip.communicator.service.protocol.Message msg + = opSetBasicIM1.createMessage(body); + + //register a listener in the op set + ImEventCollector imEvtCollector1 = new ImEventCollector(); + opSetBasicIM1.addMessageListener(imEvtCollector1); + + //register a listener in the tester agent + ImEventCollector imEvtCollector2 = new ImEventCollector(); + opSetBasicIM2.addMessageListener(imEvtCollector2); + + Contact testerAgentContact + = opSetPresence1.findContactByID(fixture.userID2); + + opSetBasicIM1.sendInstantMessage(testerAgentContact, msg); + + imEvtCollector1.waitForEvent(10000); + imEvtCollector2.waitForEvent(10000); + + opSetBasicIM1.removeMessageListener(imEvtCollector1); + opSetBasicIM2.removeMessageListener(imEvtCollector2); + + //verify that the message delivered event was dispatched + assertTrue( "No events delivered when sending a message" + , imEvtCollector1.collectedEvents.size() > 0); + + assertTrue( "Received evt was not an instance of " + + MessageDeliveredEvent.class.getName() + , imEvtCollector1.collectedEvents.get(0) + instanceof MessageDeliveredEvent); + + MessageDeliveredEvent evt + = (MessageDeliveredEvent)imEvtCollector1.collectedEvents.get(0); + assertEquals("message destination " + , evt.getDestinationContact().getAddress() + , fixture.userID2); + + assertSame("source message", msg, evt.getSourceMessage()); + + + //verify that the message has successfully arived at the destination + assertTrue( "No messages received by the tester agent" + , imEvtCollector2.collectedEvents.size() > 0); + String receivedBody = + ((MessageReceivedEvent)imEvtCollector2.collectedEvents + .get(0)).getSourceMessage().getContent(); + + assertEquals("received message body", msg.getContent(), receivedBody); + } + + /** + * Creates an Message through the simple createMessage() method and inspects + * its parameters. + */ + public void testCreateMessage1() + { + String body = "This is an IM coming from the tested implementation" + + " on " + new Date().toString(); + net.java.sip.communicator.service.protocol.Message msg + = opSetBasicIM1.createMessage(body); + + assertEquals("message body", body, msg.getContent()); + assertTrue("message body bytes" + , Arrays.equals(body.getBytes(), msg.getRawData())); + assertEquals("message length", body.length(), msg.getSize()); + assertEquals("message content type" + , OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE + , msg.getContentType()); + + assertEquals("message encoding" + , OperationSetBasicInstantMessaging.DEFAULT_MIME_ENCODING + , msg.getEncoding()); + + assertNotNull("message uid", msg.getMessageUID()); + + //a further test on message uid. + net.java.sip.communicator.service.protocol.Message msg2 + = opSetBasicIM1.createMessage(body); + assertFalse("message uid", msg.getMessageUID().equals( + msg2.getMessageUID())); + } + + /** + * Creates an Message through the advance createMessage() method and + * inspects its parameters. + */ + public void testCreateMessage2() + { + String body = "This is an IM coming from the tested implementation" + + " on " + new Date().toString(); + String contentType = "text/html"; + String encoding = "UTF-16"; + String subject = "test message"; + net.java.sip.communicator.service.protocol.Message msg + = opSetBasicIM1.createMessage( + body.getBytes(), contentType, encoding, subject); + + assertEquals("message body", body, msg.getContent()); + assertTrue("message body bytes" + , Arrays.equals(body.getBytes(), msg.getRawData())); + assertEquals("message length", body.length(), msg.getSize()); + assertEquals("message content type", contentType, msg.getContentType()); + assertEquals("message encoding", encoding, msg.getEncoding()); + assertNotNull("message uid", msg.getMessageUID()); + + //a further test on message uid. + net.java.sip.communicator.service.protocol.Message msg2 + = opSetBasicIM1.createMessage(body); + assertFalse("message uid", msg.getMessageUID().equals( + msg2.getMessageUID())); + } + + /** + * Collects instant messaging events. + */ + private class ImEventCollector implements MessageListener + { + private List collectedEvents = new LinkedList(); + /** + * Called when a new incoming <tt>Message</tt> has been received. + * @param evt the <tt>MessageReceivedEvent</tt> containing the newly + * received message, its sender and other details. + */ + public void messageReceived(MessageReceivedEvent evt) + { + logger.debug("Received a MessageReceivedEvent: " + evt); + + synchronized(this) + { + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Called to indicated that delivery of a message sent earlier has failed. + * Reason code and phrase are contained by the <tt>MessageFailedEvent</tt> + * @param evt the <tt>MessageFailedEvent</tt> containing the ID of the + * message whose delivery has failed. + */ + public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) + { + logger.debug("Received a MessageDeliveryFailedEvent: " + evt); + + synchronized(this) + { + collectedEvents.add(evt); + notifyAll(); + } + } + + + /** + * Called when the underlying implementation has received an indication + * that a message, sent earlier has been successfully received by the + * destination. + * @param evt the MessageDeliveredEvent containing the id of the message + * that has caused the event. + */ + public void messageDelivered(MessageDeliveredEvent evt) + { + logger.debug("Received a MessageDeliveredEvent: " + evt); + + synchronized(this) + { + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Blocks until at least one event is received or until waitFor + * miliseconds pass (whichever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for an event before simply bailing out. + */ + public void waitForEvent(long waitFor) + { + synchronized(this) + { + + if(collectedEvents.size() > 0) + return; + + try{ + wait(waitFor); + } + catch (InterruptedException ex) + { + logger.debug( + "Interrupted while waiting for a message evt", ex); + } + } + } + } + + /** + * A method that would simply send messages to a group of people so that + * they would get notified that tests are being run. + */ + public void testSendFunMessages() + { + String hostname = ""; + + try{ + hostname = java.net.InetAddress.getLocalHost().getHostName() + ": "; + }catch (UnknownHostException ex){} + + String message = hostname + + "Hello this is the SIP Communicator (version " + + System.getProperty("sip-communicator.version") + + ") build on: " + + new Date().toString() + + ". Have a very nice day!"; + + String list = System.getProperty("accounts.reporting.YAHOO_REPORT_LIST"); + + logger.debug("Will send message " + message + " to: " + list); + + //if no property is specified - return + if(list == null || list.trim().length() == 0) + return; + + StringTokenizer tokenizer = new StringTokenizer(list, " "); + + while(tokenizer.hasMoreTokens()) + { + String contactID = tokenizer.nextToken(); + Contact contact + = opSetPresence2.findContactByID(contactID); + + if(contact == null) + { + try + { + opSetPresence2.subscribe(contactID); + Object o = new Object(); + synchronized (o) + { + o.wait(2000); + } + } + catch (Exception ex1) + { + continue; + } + } + + contact + = opSetPresence2.findContactByID(contactID); + + opSetBasicIM2.sendInstantMessage(contact, + opSetBasicIM2.createMessage(message)); + } + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetPersistentPresence.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetPersistentPresence.java new file mode 100644 index 0000000..ffdc782 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetPersistentPresence.java @@ -0,0 +1,559 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import java.util.*; + +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * @author Damian Minkov + */ +public class TestOperationSetPersistentPresence + extends TestCase +{ + private static final Logger logger = + Logger.getLogger(TestOperationSetPersistentPresence.class); + + private YahooSlickFixture fixture = new YahooSlickFixture(); + private OperationSetPersistentPresence opSetPersPresence1 = null; + private OperationSetPersistentPresence opSetPersPresence2 = null; + private static final String testGroupName = "NewGroup"; + private static final String testGroupName2 = "Renamed"; + + public TestOperationSetPersistentPresence(String name) + { + super(name); + } + + /** + * Creates a test suite containing all tests of this class followed by + * test methods that we want executed in a specified order. + * @return the Test suite to run + */ + public static Test suite() + { + TestSuite suite = + new TestSuite(); + + //the following 2 need to be run in the specified order. + //(postTestRemoveGroup() needs the group created from + //postTestCreateGroup() ) + suite.addTest( + new TestOperationSetPersistentPresence("postTestCreateGroup")); + + //rename + //suite.addTest( new TestOperationSetPersistentPresence( + // "postTestRenameGroup")); + + suite.addTest( + new TestOperationSetPersistentPresence("postTestRemoveGroup")); + + // create the contact list + suite.addTest( + new TestOperationSetPersistentPresence("prepareContactList")); + + suite.addTestSuite(TestOperationSetPersistentPresence.class); + + return suite; + } + + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + + Map supportedOperationSets1 = + fixture.provider1.getSupportedOperationSets(); + + if ( supportedOperationSets1 == null + || supportedOperationSets1.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this Yahoo implementation. "); + + //get the operation set presence here. + opSetPersPresence1 = + (OperationSetPersistentPresence)supportedOperationSets1.get( + OperationSetPersistentPresence.class.getName()); + + //if still null then the implementation doesn't offer a presence + //operation set which is unacceptable for yahoo. + if (opSetPersPresence1 == null) + throw new NullPointerException( + "An implementation of the Yahoo service must provide an " + + "implementation of at least the one of the Presence " + + "Operation Sets"); + + // lets do it once again for the second provider + Map supportedOperationSets2 = + fixture.provider2.getSupportedOperationSets(); + + if (supportedOperationSets2 == null + || supportedOperationSets2.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + + "this Yahoo implementation. "); + + //get the operation set presence here. + opSetPersPresence2 = + (OperationSetPersistentPresence) supportedOperationSets2.get( + OperationSetPersistentPresence.class.getName()); + + //if still null then the implementation doesn't offer a presence + //operation set which is unacceptable for yahoo. + if (opSetPersPresence2 == null) + throw new NullPointerException( + "An implementation of the yahoo service must provide an " + + "implementation of at least the one of the Presence " + + "Operation Sets"); + } + + protected void tearDown() throws Exception + { + fixture.tearDown(); + super.tearDown(); + } + + /** + * Retrieves a server stored contact list and checks whether it contains + * all contacts that have been added there during the initialization + * phase by the testerAgent. + */ + public void testRetrievingServerStoredContactList() + { + ContactGroup rootGroup + = opSetPersPresence1.getServerStoredContactListRoot(); + + logger.debug("=========== Server Stored Contact List ================="); + + logger.debug("rootGroup="+rootGroup.getGroupName() + +" rootGroup.childContacts="+rootGroup.countContacts() + + "rootGroup.childGroups="+rootGroup.countSubgroups() + + "Printing rootGroupContents=\n"+rootGroup.toString()); + + Hashtable expectedContactList = fixture.preInstalledBuddyList; + + logger.debug("============== Expected Contact List ==================="); + logger.debug(expectedContactList); + + //Go through the contact list retrieved by the persistence presence set + //and remove the name of every contact and group that we find there from + //the expected contct list hashtable. + Iterator groups = rootGroup.subgroups(); + while (groups.hasNext() ) + { + ContactGroup group = (ContactGroup)groups.next(); + + List expectedContactsInGroup + = (List)expectedContactList.get(group.getGroupName()); + + // When sending the offline message + // the sever creates a group NotInContactList, + // beacuse the buddy we are sending message to is not in + // the contactlist. So this group must be ignored + // Also we must ignore the group created by default + // from the yahoo lib + if(!group.getGroupName().equals("NotInContactList") && + !group.getGroupName().equals("Default group")) + { + assertNotNull("Group " + group.getGroupName() + + " was returned by " + + + "the server but was not in the expected contact list." + , expectedContactsInGroup); + + Iterator contactsIter = group.contacts(); + while(contactsIter.hasNext()) + { + String contactID = ((Contact)contactsIter.next()). + getAddress(); + expectedContactsInGroup.remove(contactID); + } + + //If we've removed all the sub contacts, remove the group too. + if(expectedContactsInGroup.size() == 0) + expectedContactList.remove(group.getGroupName()); + } + } + + //whatever we now have in the expected contact list snapshot are groups, + //that have been added by the testerAgent but that were not retrieved + //by the persistent presence operation set. + assertTrue("The following contacts were on the server sidec contact " + +"list, but were not returned by the pers. pres. op. set" + + expectedContactList.toString() + , expectedContactList.isEmpty()); + } + + /** + * Creates a group in the server stored contact list, makes sure that the + * corresponding event has been generated and verifies that the group is + * in the list. + * + * @throws java.lang.Exception + */ + public void postTestCreateGroup() + throws Exception + { + // first clear the list + fixture.clearProvidersLists(); + + waitFor(5000); + + logger.trace("testing creation of server stored groups"); + //first add a listener + GroupChangeCollector groupChangeCollector = new GroupChangeCollector(); + opSetPersPresence1 + .addServerStoredGroupChangeListener(groupChangeCollector); + + //create the group + opSetPersPresence1.createServerStoredContactGroup( + opSetPersPresence1.getServerStoredContactListRoot(), testGroupName); + + groupChangeCollector.waitForEvent(10000); + + opSetPersPresence1 + .removeServerStoredGroupChangeListener(groupChangeCollector); + + // check whether we got group created event + assertEquals("Collected Group Change events: ", + 1, groupChangeCollector.collectedEvents.size()); + + assertEquals("Group name.", testGroupName, + ((ServerStoredGroupEvent)groupChangeCollector.collectedEvents + .get(0)).getSourceGroup().getGroupName()); + + // check whether the group is retrievable + ContactGroup group = opSetPersPresence1.getServerStoredContactListRoot() + .getGroup(testGroupName); + + assertNotNull("A newly created group was not in the contact list.", + group); + + assertEquals("New group name", testGroupName, group.getGroupName()); + + // when opearting with groups . the group must have entries + // so changes to take effect. Otherwise group will be lost after loggingout + try + { + opSetPersPresence1.subscribe(group, fixture.userID2); + + waitFor(1500); + } + catch (Exception ex) + { + fail("error adding entry to group : " + + group.getGroupName() + " " + + ex.getMessage()); + } + } + + + /** + * Removes the group created in the server stored contact list by the create + * group test, makes sure that the corresponding event has been generated + * and verifies that the group is not in the list any more. + */ + public void postTestRemoveGroup() + { + logger.trace("testing removal of server stored groups"); + + //first add a listener + GroupChangeCollector groupChangeCollector = new GroupChangeCollector(); + opSetPersPresence1 + .addServerStoredGroupChangeListener(groupChangeCollector); + + //remove the group + opSetPersPresence1.removeServerStoredContactGroup( + opSetPersPresence1.getServerStoredContactListRoot() + .getGroup(testGroupName)); + + groupChangeCollector.waitForEvent(10000); + + opSetPersPresence1 + .removeServerStoredGroupChangeListener(groupChangeCollector); + + // check whether we got group created event + assertEquals("Collected Group Change event", + 1, groupChangeCollector.collectedEvents.size()); + + assertEquals("Group name.", testGroupName, + ((ServerStoredGroupEvent)groupChangeCollector.collectedEvents + .get(0)).getSourceGroup().getGroupName()); + + // check whether the group is still on the contact list + ContactGroup group = opSetPersPresence1.getServerStoredContactListRoot() + .getGroup(testGroupName); + + assertNull("A freshly removed group was still on the contact list. - " + group, + group); + } + + /** + * Renames our test group and checks whether corresponding events are + * triggered. Verifies whether the group has really changed its name and + * whether it is findable by its new name. Also makes sure that it does + * not exist under its previous name any more. + */ + public void postTestRenameGroup() + { + logger.trace("Testing renaming groups."); + + ContactGroup group = opSetPersPresence1.getServerStoredContactListRoot() + .getGroup(testGroupName); + + //first add a listener + GroupChangeCollector groupChangeCollector = new GroupChangeCollector(); + opSetPersPresence1 + .addServerStoredGroupChangeListener(groupChangeCollector); + + //change the name and wait for a confirmation event + opSetPersPresence1.renameServerStoredContactGroup(group, testGroupName2); + + groupChangeCollector.waitForEvent(10000); + + opSetPersPresence1 + .removeServerStoredGroupChangeListener(groupChangeCollector); + + //examine the event + assertEquals("Collected Group Change event", + 1, groupChangeCollector.collectedEvents.size()); + + assertEquals("Group name.", testGroupName2, + ((ServerStoredGroupEvent)groupChangeCollector.collectedEvents + .get(0)).getSourceGroup().getGroupName()); + + // check whether the group is still on the contact list + ContactGroup oldGroup = opSetPersPresence1.getServerStoredContactListRoot() + .getGroup(testGroupName); + + assertNull("A group was still findable by its old name after renaming.", + oldGroup); + + //make sure that we could find the group by its new name. + ContactGroup newGroup = opSetPersPresence1.getServerStoredContactListRoot() + .getGroup(testGroupName2); + + assertNotNull("Could not find a renamed group by its new name.", + newGroup); + } + + /** + * Create the contact list. Later will be test to be sure that creating is ok + * @throws Exception + */ + public void prepareContactList() + throws Exception + { + fixture.clearProvidersLists(); + + waitFor(3000); + + String contactList = System.getProperty( + YahooProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME, null); + + logger.debug("The " + + YahooProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME + + " property is set to=" + contactList); + + if( contactList == null + || contactList.trim().length() < 6)//at least 4 for a UIN, 1 for the + // dot and 1 for the grp name + throw new IllegalArgumentException( + "The " + + YahooProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME + + " property did not contain a contact list."); + StringTokenizer tokenizer = new StringTokenizer(contactList, " \n\t"); + + logger.debug("tokens contained by the CL tokenized=" + +tokenizer.countTokens()); + + Hashtable contactListToCreate = new Hashtable(); + + //go over all group.uin tokens + while (tokenizer.hasMoreTokens()) + { + String groupUinToken = tokenizer.nextToken(); + int dotIndex = groupUinToken.indexOf("."); + + if ( dotIndex == -1 ) + { + throw new IllegalArgumentException(groupUinToken + + " is not a valid Group.UIN token"); + } + + String groupName = groupUinToken.substring(0, dotIndex); + String uin = groupUinToken.substring(dotIndex + 1); + + if( groupName.trim().length() < 1 + || uin.trim().length() < 4 ) + { + throw new IllegalArgumentException( + groupName + " or " + uin + + " are not a valid group name or yahoo UIN."); + } + + //check if we've already seen this group and if not - add it + List uinInThisGroup = (List)contactListToCreate.get(groupName); + if (uinInThisGroup == null) + { + uinInThisGroup = new ArrayList(); + contactListToCreate.put(groupName, uinInThisGroup); + } + + uinInThisGroup.add(uin); + } + + // now init the list + Enumeration newGroupsEnum = contactListToCreate.keys(); + + GroupChangeCollector groupChangeCollector = new GroupChangeCollector(); + opSetPersPresence1.addServerStoredGroupChangeListener(groupChangeCollector); + + //go over all groups in the contactsToAdd table + while (newGroupsEnum.hasMoreElements()) + { + String groupName = (String) newGroupsEnum.nextElement(); + logger.debug("Will add group " + groupName); + + opSetPersPresence1.createServerStoredContactGroup( + opSetPersPresence1.getServerStoredContactListRoot(), groupName); + + groupChangeCollector.waitForEvent(3000); + + ContactGroup newlyCreatedGroup = + opSetPersPresence1.getServerStoredContactListRoot().getGroup(groupName); + + Iterator contactsToAddToThisGroup + = ( (List) contactListToCreate.get(groupName)).iterator(); + while (contactsToAddToThisGroup.hasNext()) + { + String id = (String) contactsToAddToThisGroup.next(); + + logger.debug("Will add buddy " + id); + opSetPersPresence1.subscribe(newlyCreatedGroup, id); + } + } + + waitFor(2000); + + //store the created contact list for later reference + YahooSlickFixture.preInstalledBuddyList = contactListToCreate; + } + + private void waitFor(long time) + throws Exception + { + Object o = new Object(); + synchronized(o) + { + o.wait(time); + } + } + + /** + * The class would listen for and store received events delivered to + * <tt>ServerStoredGroupListener</tt>s. + */ + private class GroupChangeCollector implements ServerStoredGroupListener + { + public ArrayList collectedEvents = new ArrayList(); + + /** + * Blocks until at least one event is received or until waitFor + * miliseconds pass (whicever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for an event before simply bailing out. + */ + public void waitForEvent(long waitFor) + { + synchronized(this) + { + if(collectedEvents.size() > 0) + return; + + try{ + wait(waitFor); + } + catch (InterruptedException ex) + { + logger.debug( + "Interrupted while waiting for a subscription evt", ex); + } + } + } + + /** + * Called whnever an indication is received that a new server stored + * group is created. + * @param evt a ServerStoredGroupChangeEvent containing a reference to + * the newly created group. + */ + public void groupCreated(ServerStoredGroupEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Called when an indication is received that the name of a server stored + * contact group has changed. + * @param evt a ServerStoredGroupChangeEvent containing the details of the + * name change. + */ + public void groupNameChanged(ServerStoredGroupEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Called whnever an indication is received that an existing server stored + * group has been removed. + * @param evt a ServerStoredGroupChangeEvent containing a reference to the + * newly created group. + */ + public void groupRemoved(ServerStoredGroupEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Called whnever an indication is received that an existing server + * stored group has been resolved. + * @param evt a ServerStoredGroupChangeEvent containing a reference to + * the resolved group. + */ + public void groupResolved(ServerStoredGroupEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetPresence.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetPresence.java new file mode 100644 index 0000000..caa2df4 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetPresence.java @@ -0,0 +1,989 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import java.beans.*; +import java.util.*; + +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.yahooconstants.*; +import net.java.sip.communicator.util.*; + +/** + * Tests yahoo implementations of a Presence Operation Set. Tests in this class + * verify functionality such as: Changing local (our own) status and + * corresponding event dispatching; Querying status of contacts, Subscribing + * for presence notifications upong status changes of specific contacts. + * <p> + * Using a custom suite() method, we make sure that apart from standard test + * methods (those with a <tt>test</tt> prefix) we also execute those that + * we want run in a specific order like for example - postTestSubscribe() and + * postTestUnsubscribe(). + * <p> + * @author Damian Minkov + */ +public class TestOperationSetPresence + extends TestCase +{ + private static final Logger logger = + Logger.getLogger(TestOperationSetPresence.class); + + private YahooSlickFixture fixture = new YahooSlickFixture(); + private OperationSetPresence operationSetPresence1 = null; + private OperationSetPresence operationSetPresence2 = null; + private String statusMessageRoot = new String("Our status is now: "); + + private AuthHandler authHandler1 = null; + private AuthHandler authHandler2 = null; + + public TestOperationSetPresence(String name) + { + super(name); + } + + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + + Map supportedOperationSets1 = + fixture.provider1.getSupportedOperationSets(); + + if ( supportedOperationSets1 == null + || supportedOperationSets1.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this implementation. "); + + //get the operation set presence here. + operationSetPresence1 = + (OperationSetPresence)supportedOperationSets1.get( + OperationSetPresence.class.getName()); + + //if the op set is null then the implementation doesn't offer a presence + //operation set which is unacceptable for yahoo. + if (operationSetPresence1 == null) + { + throw new NullPointerException( + "An implementation of the yahoo service must provide an " + + "implementation of at least the one of the Presence " + + "Operation Sets"); + } + + // do it once again for the second provider + Map supportedOperationSets2 = + fixture.provider2.getSupportedOperationSets(); + + if ( supportedOperationSets2 == null + || supportedOperationSets2.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this yahoo implementation. "); + + //get the operation set presence here. + operationSetPresence2 = + (OperationSetPresence)supportedOperationSets2.get( + OperationSetPresence.class.getName()); + + //if the op set is null then the implementation doesn't offer a presence + //operation set which is unacceptable for yahoo. + if (operationSetPresence2 == null) + { + throw new NullPointerException( + "An implementation of the yahoo service must provide an " + + "implementation of at least the one of the Presence " + + "Operation Sets"); + } + + if(authHandler1 == null) + { + authHandler1 = new AuthHandler(operationSetPresence1); + operationSetPresence1.setAuthorizationHandler(authHandler1); + } + + if(authHandler2 == null) + { + authHandler2 = new AuthHandler(operationSetPresence2); + operationSetPresence2.setAuthorizationHandler(authHandler2); + } + } + + protected void tearDown() throws Exception + { + super.tearDown(); + + fixture.tearDown(); + } + + /** + * Creates a test suite containing all tests of this class followed by + * test methods that we want executed in a specified order. + * @return Test + */ + public static Test suite() + { + //return an (almost) empty suite if we're running in offline mode. + if(YahooSlickFixture.onlineTestingDisabled) + { + TestSuite suite = new TestSuite(); + //the only test around here that we could run without net + //connectivity + suite.addTest( + new TestOperationSetPresence( + "testSupportedStatusSetForCompleteness")); + return suite; + } + + TestSuite suite = new TestSuite(); + + // clear the lists before subscribing users + suite.addTest(new TestOperationSetPresence("clearLists")); + + // first postTestSubscribe. to be sure that contacts are in the + // list so we can further continue and test presences each other + suite.addTest(new TestOperationSetPresence("postTestSubscribe")); + + // add other tests + suite.addTestSuite(TestOperationSetPresence.class); + + // now test unsubscribe + suite.addTest(new TestOperationSetPresence("postTestUnsubscribe")); + + return suite; + } + + /** + * Verifies that all necessary yahoo test states are supported by the + * implementation. + */ + public void testSupportedStatusSetForCompleteness() + { + //first create a local list containing the presence status instances + //supported by the underlying implementation. + Iterator supportedStatusSetIter = + operationSetPresence1.getSupportedStatusSet(); + + List supportedStatusSet = new LinkedList(); + while (supportedStatusSetIter.hasNext()){ + supportedStatusSet.add(supportedStatusSetIter.next()); + } + + //create a copy of the MUST status set and remove any matching status + //that is also present in the supported set. + List requiredStatusSetCopy = (List)YahooStatusEnum.yahooStatusSet.clone(); + + requiredStatusSetCopy.removeAll(supportedStatusSet); + + //if we have anything left then the implementation is wrong. + int unsupported = requiredStatusSetCopy.size(); + assertTrue( "There are " + unsupported + " statuses as follows:" + + requiredStatusSetCopy, + unsupported == 0); + } + + /** + * Verify that changing state to STEPPED_OUT works as supposed to and that it + * generates the corresponding event. + * @throws Exception in case a failure occurs while the operation set + * is switching to the new state. + */ + public void testChangingStateToSteppedOut() throws Exception + { + subtestStateTransition(YahooStatusEnum.STEPPED_OUT); + } + + /** + * Verify that changing state to NOT_IN_OFFICE works as supposed to and that it + * generates the corresponding event. + * @throws Exception in case a failure occurs while the operation set + * is switching to the new state. + */ + public void testChangingStateToNotInOffice() throws Exception + { + subtestStateTransition(YahooStatusEnum.NOT_IN_OFFICE); + } + + /** + * Verify that changing state to BUSY works as supposed to and that it + * generates the corresponding event. + * @throws Exception in case a failure occurs while the operation set + * is switching to the new state. + */ + public void testChangingStateToBusy() throws Exception + { + subtestStateTransition(YahooStatusEnum.BUSY); + } + + /** + * Verify that changing state to FREE_FOR_CHAT works as supposed to and that it + * generates the corresponding event. + * @throws Exception in case a failure occurs while the operation set + * is switching to the new state. + */ + public void testChangingStateToIdle() throws Exception + { + subtestStateTransition(YahooStatusEnum.IDLE); + } + + /** + * Verify that changing state to ONLINE works as supposed to and that it + * generates the corresponding event. + * @throws Exception in case a failure occurs while the operation set + * is switching to the new state. + */ + public void testChangingStateToOnline() throws Exception + { + subtestStateTransition(YahooStatusEnum.AVAILABLE); + } + + /** + * Verify that changing state to OUT_TO_LUNCH works as supposed to and that it + * generates the corresponding event. + * @throws Exception in case a failure occurs while the operation set + * is switching to the new state. + */ + public void testChangingStateToOutToLunch() throws Exception + { + subtestStateTransition(YahooStatusEnum.OUT_TO_LUNCH); + } + + /** + * Verify that changing state to ON_THE_PHONE works as supposed to and that it + * generates the corresponding event. + * @throws Exception in case a failure occurs while the operation set + * is switching to the new state. + */ + public void testChangingStateToOnThePhone() throws Exception + { + subtestStateTransition(YahooStatusEnum.ON_THE_PHONE); + } + + /** + * Used by methods testing state transiotions + * + * @param newStatus the YahooStatusEnum field corresponding to the status + * that we'd like the opeation set to enter. + * + * @throws Exception in case changing the state causes an exception + */ + public void subtestStateTransition( YahooStatusEnum newStatus) + throws Exception + { + logger.trace(" --=== beginning state transition test ===--"); + + PresenceStatus oldStatus = operationSetPresence1.getPresenceStatus(); + + logger.debug( "old status is=" + oldStatus.getStatusName() + + " new status=" + newStatus.getStatusName()); + + //First register a listener to make sure that all corresponding + //events have been generated. + PresenceStatusEventCollector statusEventCollector + = new PresenceStatusEventCollector(); + operationSetPresence1.addProviderPresenceStatusListener( + statusEventCollector); + + //change the status + operationSetPresence1.publishPresenceStatus(newStatus, null); + pauseAfterStateChanges(); + + //test event notification. + statusEventCollector.waitForPresEvent(10000); + + operationSetPresence1.removeProviderPresenceStatusListener( + statusEventCollector); + + assertEquals("Events dispatched during an event transition.", + 1, statusEventCollector.collectedPresEvents.size()); + assertEquals("A status changed event contained wrong old status.", + oldStatus, + ((ProviderPresenceStatusChangeEvent) + statusEventCollector.collectedPresEvents.get(0)) + .getOldStatus()); + assertEquals("A status changed event contained wrong new status.", + newStatus, + ((ProviderPresenceStatusChangeEvent) + statusEventCollector.collectedPresEvents.get(0)) + .getNewStatus()); + + // verify that the operation set itself is aware of the status change + assertEquals("opSet.getPresenceStatus() did not return properly.", + newStatus, + operationSetPresence1.getPresenceStatus()); + + YahooStatusEnum actualStatus = (YahooStatusEnum) + operationSetPresence2.queryContactStatus(fixture.userID1); + + assertEquals("The underlying implementation did not switch to the " + +"requested presence status.", + newStatus, + actualStatus); + + logger.trace(" --=== finished test ===--"); + } + + /** + * Give time changes to take effect + */ + private void pauseAfterStateChanges() + { + try + { + Thread.currentThread().sleep(1500); + } + catch (InterruptedException ex) + { + logger.debug("Pausing between state changes was interrupted", ex); + } + } + /** + * Verifies that querying status works fine. The tester agent would + * change status and the operation set would have to return the right status + * after every change. + * + * @throws java.lang.Exception if one of the transitions fails + */ + public void testQueryContactStatus() + throws Exception + { + // --- NA --- + logger.debug("Will Query an BRB contact."); + subtestQueryContactStatus(YahooStatusEnum.BE_RIGHT_BACK, + YahooStatusEnum.BE_RIGHT_BACK); + + // --- DND --- + logger.debug("Will Query a Busy contact."); + subtestQueryContactStatus(YahooStatusEnum.BUSY, + YahooStatusEnum.BUSY); + + // --- FFC --- + logger.debug("Will Query a Idle contact."); + subtestQueryContactStatus(YahooStatusEnum.IDLE, + YahooStatusEnum.IDLE); + + // --- INVISIBLE --- + logger.debug("Will Query an Invisible contact."); + subtestQueryContactStatus(YahooStatusEnum.INVISIBLE, + YahooStatusEnum.OFFLINE); + + // --- Online --- + logger.debug("Will Query an Online contact."); + subtestQueryContactStatus(YahooStatusEnum.AVAILABLE, + YahooStatusEnum.AVAILABLE); + } + + /** + * Used by functions testing the queryContactStatus method of the + * presence operation set. + * @param status the status as specified, that + * the tester agent should switch to. + * @param expectedReturn the PresenceStatus that the presence operation + * set should see the tester agent in once it has switched to taStatusLong. + * + * @throws java.lang.Exception if querying the status causes some exception. + */ + public void subtestQueryContactStatus(PresenceStatus status, + PresenceStatus expectedReturn) + throws Exception + { + operationSetPresence2.publishPresenceStatus(status, "status message"); + + pauseAfterStateChanges(); + + PresenceStatus actualReturn + = operationSetPresence1.queryContactStatus(fixture.userID2); + assertEquals("Querying a " + + expectedReturn.getStatusName() + + " state did not return as expected" + , expectedReturn, actualReturn); + } + + /** + * The method would add a subscription for a contact, wait for a + * subscription event confirming the subscription, then change the status + * of the newly added contact (which is actually the testerAgent) and + * make sure that the corresponding notification events have been generated. + * + * @throws java.lang.Exception if an exception occurs during testing. + */ + public void postTestSubscribe() + throws Exception + { + logger.debug("Testing Subscription and Subscription Event Dispatch."); + + SubscriptionEventCollector subEvtCollector + = new SubscriptionEventCollector(); + operationSetPresence1.addSubsciptionListener(subEvtCollector); + + + synchronized (subEvtCollector){ + operationSetPresence1.subscribe(fixture.userID2); + //we may already have the event, but it won't hurt to check. + subEvtCollector.waitForEvent(10000); + operationSetPresence1.removeSubscriptionListener(subEvtCollector); + } + + assertEquals("Subscription event dispatching failed." + , 1, subEvtCollector.collectedEvents.size()); + SubscriptionEvent subEvt = + (SubscriptionEvent)subEvtCollector.collectedEvents.get(0); + + assertEquals("SubscriptionEvent Source:", + fixture.userID2, + ((Contact)subEvt.getSource()).getAddress()); + assertEquals("SubscriptionEvent Source Contact:", + fixture.userID2, + subEvt.getSourceContact().getAddress()); + assertSame("SubscriptionEvent Source Provider:", + fixture.provider1, + subEvt.getSourceProvider()); + + subEvtCollector.collectedEvents.clear(); + + // make the user agent tester change its states and make sure we are + // notified + logger.debug("Testing presence notifications."); + YahooStatusEnum oldStatus + = (YahooStatusEnum)operationSetPresence2.getPresenceStatus(); + + + YahooStatusEnum newStatus = YahooStatusEnum.IDLE; + + //in case we are by any chance already in a FREE_FOR_CHAT status, we'll + //be changing to something else + if(oldStatus.equals(newStatus)){ + newStatus = YahooStatusEnum.BUSY; + } + + //now do the actual status notification testing + ContactPresenceEventCollector contactPresEvtCollector + = new ContactPresenceEventCollector( + fixture.userID2, newStatus); + operationSetPresence1.addContactPresenceStatusListener( + contactPresEvtCollector); + + synchronized (contactPresEvtCollector){ + operationSetPresence2.publishPresenceStatus(newStatus, "new status"); + //we may already have the event, but it won't hurt to check. + contactPresEvtCollector.waitForEvent(10000); + operationSetPresence1 + .removeContactPresenceStatusListener(contactPresEvtCollector); + } + + assertEquals("Presence Notif. event dispatching failed." + , 1, contactPresEvtCollector.collectedEvents.size()); + ContactPresenceStatusChangeEvent presEvt = + (ContactPresenceStatusChangeEvent) + contactPresEvtCollector.collectedEvents.get(0); + + assertEquals("Presence Notif. event Source:", + fixture.userID2, + ((Contact)presEvt.getSource()).getAddress()); + assertEquals("Presence Notif. event Source Contact:", + fixture.userID2, + presEvt.getSourceContact().getAddress()); + assertSame("Presence Notif. event Source Provider:", + fixture.provider1, + presEvt.getSourceProvider()); + + PresenceStatus reportedNewStatus = presEvt.getNewStatus(); + PresenceStatus reportedOldStatus = presEvt.getOldStatus(); + + assertEquals( "Reported new PresenceStatus: ", + newStatus, reportedNewStatus ); + + //don't require equality between the reported old PresenceStatus and + //the actual presence status of the tester agent because a first + //notification is not supposed to have the old status as it really was. + assertNotNull( "Reported old PresenceStatus: ", reportedOldStatus ); + + try + { + // add the the user to the reverse side needed for status tests + subEvtCollector.collectedEvents.clear(); + operationSetPresence2.addSubsciptionListener(subEvtCollector); + + synchronized (subEvtCollector) + { + operationSetPresence2.subscribe(fixture.userID1); + //we may already have the event, but it won't hurt to check. + subEvtCollector.waitForEvent(10000); + operationSetPresence2.removeSubscriptionListener( + subEvtCollector); + } + } + catch (OperationFailedException ex) + { + // happens if the user is already subscribed + } + } + + /** + * We unsubscribe from presence notification deliveries concerning + * testerAgent's presence status and verify that we receive the + * subscription removed event. We then make the tester agent change status + * and make sure that no notifications are delivered. + * + * @throws java.lang.Exception in case unsubscribing fails. + */ + public void postTestUnsubscribe() + throws Exception + { + logger.debug("Testing Unsubscribe and unsubscription event dispatch."); + + // First create a subscription and verify that it really gets created. + SubscriptionEventCollector subEvtCollector + = new SubscriptionEventCollector(); + operationSetPresence1.addSubsciptionListener(subEvtCollector); + + Contact yahooTesterAgentContact = operationSetPresence1 + .findContactByID(fixture.userID2); + + assertNotNull( + "Failed to find an existing subscription for the tester agent" + , yahooTesterAgentContact); + + synchronized(subEvtCollector){ + operationSetPresence1.unsubscribe(yahooTesterAgentContact); + subEvtCollector.waitForEvent(10000); + //don't want any more events + operationSetPresence1.removeSubscriptionListener(subEvtCollector); + } + + assertEquals("Subscription event dispatching failed." + , 1, subEvtCollector.collectedEvents.size()); + SubscriptionEvent subEvt = + (SubscriptionEvent)subEvtCollector.collectedEvents.get(0); + + assertEquals("SubscriptionEvent Source:", + yahooTesterAgentContact, subEvt.getSource()); + + assertEquals("SubscriptionEvent Source Contact:", + yahooTesterAgentContact, subEvt.getSourceContact()); + + assertSame("SubscriptionEvent Source Provider:", + fixture.provider1, + subEvt.getSourceProvider()); + + subEvtCollector.collectedEvents.clear(); + + // make the user agent tester change its states and make sure we don't + // get notifications as we're now unsubscribed. + logger.debug("Testing (lack of) presence notifications."); + YahooStatusEnum oldStatus + = (YahooStatusEnum)operationSetPresence2.getPresenceStatus(); + YahooStatusEnum newStatus = YahooStatusEnum.IDLE; + + //in case we are by any chance already in a FREE_FOR_CHAT status, we'll + //be changing to something else + if(oldStatus.equals(newStatus)){ + newStatus = YahooStatusEnum.BUSY; + } + + //now do the actual status notification testing + ContactPresenceEventCollector contactPresEvtCollector + = new ContactPresenceEventCollector(fixture.userID2, null); + operationSetPresence1.addContactPresenceStatusListener( + contactPresEvtCollector); + + synchronized (contactPresEvtCollector){ + operationSetPresence2.publishPresenceStatus(newStatus, "new status"); + + //we may already have the event, but it won't hurt to check. + contactPresEvtCollector.waitForEvent(10000); + operationSetPresence1 + .removeContactPresenceStatusListener(contactPresEvtCollector); + } + + assertEquals("Presence Notifications were received after unsubscibing." + , 0, contactPresEvtCollector.collectedEvents.size()); + } + + public void clearLists() + throws Exception + { + logger.debug("Clear the two lists before tests"); + + // wait for a moment + // give time the impl to get the lists + logger.debug("start clearing"); + fixture.clearProvidersLists(); + + Object o = new Object(); + synchronized(o) + { + o.wait(3000); + } + } + + /** + * An event collector that would collect all events generated by a + * provider after a status change. The collector would also do a notidyAll + * every time it receives an event. + */ + private class PresenceStatusEventCollector + implements ProviderPresenceStatusListener + { + public ArrayList collectedPresEvents = new ArrayList(); + public ArrayList collectedStatMsgEvents = new ArrayList(); + + public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedPresEvents.size()+")= "+evt); + collectedPresEvents.add(evt); + notifyAll(); + } + } + + public void providerStatusMessageChanged(PropertyChangeEvent evt) + { + synchronized(this) + { + logger.debug("Collected stat.msg. evt(" + +collectedPresEvents.size()+")= "+evt); + collectedStatMsgEvents.add(evt); + notifyAll(); + } + } + + /** + * Blocks until at least one event is received or until waitFor + * miliseconds pass (whicever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for an event before simply bailing out. + */ + public void waitForPresEvent(long waitFor) + { + logger.trace("Waiting for a change in provider status."); + synchronized(this) + { + if(collectedPresEvents.size() > 0){ + logger.trace("Change already received. " + collectedPresEvents); + return; + } + + try{ + wait(waitFor); + if(collectedPresEvents.size() > 0) + logger.trace("Received a change in provider status."); + else + logger.trace("No change received for "+waitFor+"ms."); + } + catch (InterruptedException ex){ + logger.debug("Interrupted while waiting for a provider evt" + , ex); + } + } + } + + /** + * Blocks until at least one staus message event is received or until + * waitFor miliseconds pass (whichever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for a status message event before simply bailing out. + */ + public void waitForStatMsgEvent(long waitFor) + { + logger.trace("Waiting for a provider status message event."); + synchronized(this) + { + if(collectedStatMsgEvents.size() > 0){ + logger.trace("Stat msg. evt already received. " + + collectedStatMsgEvents); + return; + } + + try{ + wait(waitFor); + if(collectedStatMsgEvents.size() > 0) + logger.trace("Received a prov. stat. msg. evt."); + else + logger.trace("No prov. stat msg. received for " + +waitFor+"ms."); + } + catch (InterruptedException ex){ + logger.debug("Interrupted while waiting for a status msg evt" + , ex); + } + } + } + } + + /** + * The class would listen for and store received subscription modification + * events. + */ + private class SubscriptionEventCollector implements SubscriptionListener + { + public ArrayList collectedEvents = new ArrayList(); + + /** + * Blocks until at least one event is received or until waitFor + * miliseconds pass (whicever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for an event before simply bailing out. + */ + public void waitForEvent(long waitFor) + { + synchronized(this) + { + if(collectedEvents.size() > 0) + return; + + try{ + wait(waitFor); + } + catch (InterruptedException ex) + { + logger.debug( + "Interrupted while waiting for a subscription evt", ex); + } + } + } + + /** + * Stores the received subsctiption and notifies all waiting on this + * object + * @param evt the SubscriptionEvent containing the corresponding contact + */ + public void subscriptionCreated(SubscriptionEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Stores the received subsctiption and notifies all waiting on this + * object + * @param evt the SubscriptionEvent containing the corresponding contact + */ + public void subscriptionRemoved(SubscriptionEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Stores the received subsctiption and notifies all waiting on this + * object + * @param evt the SubscriptionEvent containing the corresponding contact + */ + public void contactModified(ContactPropertyChangeEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + + /** + * Stores the received subsctiption and notifies all waiting on this + * object + * @param evt the SubscriptionEvent containing the corresponding contact + */ + public void subscriptionMoved(SubscriptionMovedEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Stores the received subsctiption and notifies all waiting on this + * object + * @param evt the SubscriptionEvent containing the corresponding contact + */ + public void subscriptionFailed(SubscriptionEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Stores the received subsctiption and notifies all waiting on this + * object + * @param evt the SubscriptionEvent containing the corresponding contact + */ + public void subscriptionResolved(SubscriptionEvent evt) + { + synchronized(this) + { + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + + } + + /** + * The class would listen for and store received events caused by changes + * in contact presence states. + */ + private class ContactPresenceEventCollector + implements ContactPresenceStatusListener + { + public ArrayList collectedEvents = new ArrayList(); + private String trackedScreenName = null; + private YahooStatusEnum status = null; + + ContactPresenceEventCollector(String screenname, + YahooStatusEnum wantedStatus) + { + this.trackedScreenName = screenname; + this.status = wantedStatus; + } + + /** + * Blocks until at least one event is received or until waitFor + * miliseconds pass (whicever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for an event before simply bailing out. + */ + public void waitForEvent(long waitFor) + { + synchronized(this) + { + if(collectedEvents.size() > 0) + return; + + try{ + wait(waitFor); + } + catch (InterruptedException ex) + { + logger.debug( + "Interrupted while waiting for a subscription evt", ex); + } + } + } + + /** + * Stores the received status change event and notifies all waiting on + * this object + * @param evt the SubscriptionEvent containing the corresponding contact + */ + public void contactPresenceStatusChanged( + ContactPresenceStatusChangeEvent evt) + { + synchronized(this) + { + //if the user has specified event details and the received + //event does not match - then ignore it. + if( this.trackedScreenName != null + && !evt.getSourceContact().getAddress() + .equals(trackedScreenName)) + return; + if( status != null + && status != evt.getNewStatus()) + return; + + logger.debug("Collected evt("+collectedEvents.size()+")= "+evt); + collectedEvents.add(evt); + notifyAll(); + } + } + } + + /** + * Used to wait till buddy is removed from our contact list. + * Used in the authorization process tests + */ + private class UnsubscribeWait implements SubscriptionListener + { + public void waitForUnsubscribre(long waitFor) + { + synchronized(this) + { + try{ + wait(waitFor); + } + catch (InterruptedException ex) + { + logger.debug( + "Interrupted while waiting for a subscription evt", ex); + } + } + } + + public void subscriptionRemoved(SubscriptionEvent evt) + { + synchronized(this) + { + logger.debug("Got subscriptionRemoved " + evt); + notifyAll(); + } + } + + public void subscriptionCreated(SubscriptionEvent evt) + {} + public void subscriptionFailed(SubscriptionEvent evt) + {} + public void subscriptionMoved(SubscriptionMovedEvent evt) + {} + public void subscriptionResolved(SubscriptionEvent evt) + {} + public void contactModified(ContactPropertyChangeEvent evt) + {} + } + + /** + * AuthorizationHandler which accepts all requests! + */ + private class AuthHandler + implements AuthorizationHandler + { + private OperationSetPresence opset = null; + AuthHandler(OperationSetPresence opset) + { + this.opset = opset; + } + + public AuthorizationResponse processAuthorisationRequest( + AuthorizationRequest req, Contact sourceContact) + { + try{ + opset.subscribe(sourceContact.getAddress()); + }catch(Exception ex){} + + return + new AuthorizationResponse(AuthorizationResponse.ACCEPT, ""); + } + public AuthorizationRequest createAuthorizationRequest(Contact contact ) + { + return new AuthorizationRequest(); + } + public void processAuthorizationResponse( + AuthorizationResponse response, Contact sourceContact){} + } +}
\ No newline at end of file diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetTypingNotifications.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetTypingNotifications.java new file mode 100644 index 0000000..1e05ca9 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestOperationSetTypingNotifications.java @@ -0,0 +1,293 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import java.util.*; + +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Tests functionality of the typing notifications operation set. + * + * @author Damian Minkov + */ +public class TestOperationSetTypingNotifications + extends TestCase +{ + private static final Logger logger = + Logger.getLogger(TestOperationSetTypingNotifications.class); + + private YahooSlickFixture fixture = new YahooSlickFixture(); + private OperationSetTypingNotifications opSetTypingNotifs1 = null; + private OperationSetPresence opSetPresence1 = null; + private OperationSetTypingNotifications opSetTypingNotifs2 = null; + private OperationSetPresence opSetPresence2 = null; + + private OperationSetBasicInstantMessaging opSetBasicIM1 = null; + private OperationSetBasicInstantMessaging opSetBasicIM2 = null; + + + public TestOperationSetTypingNotifications(String name) + { + super(name); + } + + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + + Map supportedOperationSets1 = + fixture.provider1.getSupportedOperationSets(); + + if ( supportedOperationSets1 == null + || supportedOperationSets1.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this implementation. "); + + //get the operation set presence here. + opSetTypingNotifs1 = + (OperationSetTypingNotifications)supportedOperationSets1.get( + OperationSetTypingNotifications.class.getName()); + + //if the op set is null then the implementation doesn't offer a typing.n + //operation set which is unacceptable. + if (opSetTypingNotifs1 == null) + { + throw new NullPointerException( + "No implementation for typing notifications was found"); + } + + opSetBasicIM1 = + (OperationSetBasicInstantMessaging)supportedOperationSets1.get( + OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM1 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + + //we also need the presence op set in order to retrieve contacts. + opSetPresence1 = + (OperationSetPresence)supportedOperationSets1.get( + OperationSetPresence.class.getName()); + + //if the op set is null show that we're not happy. + if (opSetPresence1 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + + Map supportedOperationSets2 = + fixture.provider2.getSupportedOperationSets(); + + if ( supportedOperationSets2 == null + || supportedOperationSets2.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this implementation. "); + + //get the operation set presence here. + opSetTypingNotifs2 = + (OperationSetTypingNotifications)supportedOperationSets2.get( + OperationSetTypingNotifications.class.getName()); + + //if the op set is null then the implementation doesn't offer a typing.n + //operation set which is unacceptable for. + if (opSetTypingNotifs2 == null) + { + throw new NullPointerException( + "No implementation for typing notifications was found"); + } + + opSetBasicIM2 = + (OperationSetBasicInstantMessaging)supportedOperationSets2.get( + OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM2 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + + //we also need the presence op set in order to retrieve contacts. + opSetPresence2 = + (OperationSetPresence)supportedOperationSets2.get( + OperationSetPresence.class.getName()); + + //if the op set is null show that we're not happy. + if (opSetPresence2 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + } + + /** + * Create the list to be sure that contacts exchanging messages + * exists in each other lists + * @throws Exception + */ + public void prepareContactList() throws Exception + { + // be sure that contacts are in their lists + try{ + opSetPresence1.subscribe(fixture.userID2); + } + catch (OperationFailedException ex){ + // the contact already exist its OK + } + + try{ + opSetPresence2.subscribe(fixture.userID1); + } + catch (OperationFailedException ex1){ + // the contact already exist its OK + } + + Object o = new Object(); + synchronized (o) + { + o.wait(2000); + } + } + + protected void tearDown() throws Exception + { + super.tearDown(); + + fixture.tearDown(); + } + + /** + * Creates a test suite containing tests of this class in a specific order. + * We'll first execute a test where we receive a typing notification, and + * a volatile contact is created for the sender. we'll then be able to + * retrieve this volatile contact and them a notification on our turn. + * We need to do things this way as the contact corresponding to the tester + * agent has been removed in the previous test and we no longer have it + * in our contact list. + * + * @return Test a testsuite containing all tests to execute. + */ + public static Test suite() + { + TestSuite suite = new TestSuite(); + + suite.addTest(new TestOperationSetTypingNotifications( + "prepareContactList")); + + //the following 2 need to be run in the specified order. + suite.addTest(new TestOperationSetTypingNotifications( + "testTypingNotificationsEventDelivery")); + return suite; + } + + /** + * Sends a typing notification and verifies + * whether it is properly received by the tested implementation + */ + public void testTypingNotificationsEventDelivery() + { + TypingEventCollector evtCollector = new TypingEventCollector(); + + // send message so request for receiving notifications also to be set + Contact notifingContact = + opSetPresence1.findContactByID(fixture.userID2); + opSetBasicIM1.sendInstantMessage(notifingContact, + opSetBasicIM1.createMessage("ping")); + + opSetTypingNotifs1.addTypingNotificationsListener(evtCollector); + + Contact contactToNotify = + opSetPresence2.findContactByID(fixture.userID1); + + opSetBasicIM2.sendInstantMessage(contactToNotify, + opSetBasicIM2.createMessage("pong")); + + opSetTypingNotifs2.sendTypingNotification( + contactToNotify, OperationSetTypingNotifications.STATE_TYPING); + + evtCollector.waitForEvent(10000); + + opSetTypingNotifs1.removeTypingNotificationsListener(evtCollector); + + //check event dispatching + assertTrue("Number of typing events received was zero." + , evtCollector.collectedEvents.size() > 0); + + TypingNotificationEvent evt = (TypingNotificationEvent)evtCollector + .collectedEvents.get(0); + + assertEquals("Source of the typing notification event" + , fixture.userID2 + , evt.getSourceContact().getAddress() ); + + assertEquals("Source of the typing notification event" + , OperationSetTypingNotifications.STATE_TYPING + , evt.getTypingState()); + } + + /** + * Simply collects allre received events and provides a mechanisim for + * waiting for the next event. + */ + private class TypingEventCollector implements TypingNotificationsListener + { + private List collectedEvents = new LinkedList(); + /** + * Called to indicate that a remote <tt>Contact</tt> has sent us a typing + * notification. The method adds the <tt>event</tt> to the list of + * captured events. + * @param event a <tt>TypingNotificationEvent</tt> containing the sender + * of the notification and its type. + */ + public void typingNotificationReceifed(TypingNotificationEvent event) + { + logger.debug("Received a typing notification: " + event); + synchronized (this) + { + collectedEvents.add(event); + notifyAll(); + } + } + + /** + * Blocks until at least one event is received or until waitFor + * miliseconds pass (whicever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for an event before simply bailing out. + */ + public void waitForEvent(long waitFor) + { + synchronized(this){ + + if(collectedEvents.size() > 0) + return; + + try{ + wait(waitFor); + } + catch (InterruptedException ex){ + logger.debug( + "Interrupted while waiting for a subscription evt", ex); + } + } + } + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/TestProtocolProviderServiceYahooImpl.java b/test/net/java/sip/communicator/slick/protocol/yahoo/TestProtocolProviderServiceYahooImpl.java new file mode 100644 index 0000000..e5be043 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/TestProtocolProviderServiceYahooImpl.java @@ -0,0 +1,285 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import java.util.*; + +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Performs testing on protocol provider methods. + * @todo add more detailed docs once the tests are written. + * @author Damian Minkov + */ +public class TestProtocolProviderServiceYahooImpl + extends TestCase +{ + private static final Logger logger = + Logger.getLogger(TestProtocolProviderServiceYahooImpl.class); + + private YahooSlickFixture fixture = new YahooSlickFixture(); + + /** + * An event adapter that would collec registation state change events + */ + public RegistrationEventCollector regEvtCollector1 + = new RegistrationEventCollector(); + + /** + * An event adapter that would collec registation state change events + */ + public RegistrationEventCollector regEvtCollector2 + = new RegistrationEventCollector(); + + /** + * Creates a test encapsulator for the method with the specified name. + * @param name the name of the method this test should run. + */ + public TestProtocolProviderServiceYahooImpl(String name) + { + super(name); + } + + /** + * Initializes the fixture. + * @throws Exception if super.setUp() throws one. + */ + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + } + + /** + * Tears the fixture down. + * @throws Exception if fixture.tearDown() fails. + */ + protected void tearDown() throws Exception + { + fixture.tearDown(); + super.tearDown(); + } + + /** + * Makes sure that the instance of the Yahoo protocol provider that we're + * going to use for testing is properly initialized and registered with + * a Yahoo registrar. This MUST be called before any other online testing + * of the Yahoo provider so that we won't have to reregister for every single + * test. + * <p> + * The method also verifies that a registration event is fired upon + * succesful registration and collected by our event collector. + * + * @throws OperationFailedException if provider.register() fails. + */ + public void testRegister() + throws OperationFailedException + { + //add an event collector that will collect all events during the + //registration and allow us to later inspect them and make sure + //they were properly dispatched. + fixture.provider1.addRegistrationStateChangeListener(regEvtCollector1); + fixture.provider2.addRegistrationStateChangeListener(regEvtCollector2); + + //register both our providers + fixture.provider1.register(new SecurityAuthorityImpl( + System.getProperty(YahooProtocolProviderServiceLick.ACCOUNT_1_PREFIX + + ProtocolProviderFactory.PASSWORD).toCharArray())); + fixture.provider2.register(new SecurityAuthorityImpl( + System.getProperty(YahooProtocolProviderServiceLick.ACCOUNT_2_PREFIX + + ProtocolProviderFactory.PASSWORD).toCharArray())); + + //give it enough time to register. We won't really have to wait all this + //time since the registration event collector would notify us the moment + //we get signed on. + logger.debug("Waiting for registration to complete ..."); + + regEvtCollector1.waitForEvent(15000); + regEvtCollector2.waitForEvent(40000); + + //make sure that the registration process trigerred the corresponding + //events. + assertTrue( + "No events were dispatched during the registration process." + ,regEvtCollector1.collectedNewStates.size() > 0); + + assertTrue( + "No registration event notifying of registration was dispatched. " + +"All events were: " + regEvtCollector1.collectedNewStates + ,regEvtCollector1.collectedNewStates + .contains(RegistrationState.REGISTERED)); + + //now the same for provider 2 + assertTrue( + "No events were dispatched during the registration process " + +"of provider2." + ,regEvtCollector2.collectedNewStates.size() > 0); + + assertTrue( + "No registration event notifying of registration was dispatched. " + +"All events were: " + regEvtCollector2.collectedNewStates + ,regEvtCollector2.collectedNewStates + .contains(RegistrationState.REGISTERED)); + + + fixture.provider1 + .removeRegistrationStateChangeListener(regEvtCollector1); + fixture.provider2 + .removeRegistrationStateChangeListener(regEvtCollector2); + } + + + /** + * Verifies that all operation sets have the type they are declarded to + * have. + * + * @throws java.lang.Exception if a class indicated in one of the keys + * could not be forName()ed. + */ + public void testOperationSetTypes() throws Exception + { + Map supportedOperationSets + = fixture.provider1.getSupportedOperationSets(); + + //make sure that keys (which are supposed to be class names) correspond + //what the class of the values recorded against them. + Iterator setNames = supportedOperationSets.keySet().iterator(); + while (setNames.hasNext()) + { + String setName = (String) setNames.next(); + Object opSet = supportedOperationSets.get(setName); + + assertTrue(opSet + " was not an instance of " + + setName + " as declared" + , Class.forName(setName).isInstance(opSet)); + } + } + + /** + * A class that would plugin as a registration listener to a protocol + * provider and simply record all events that it sees and notifyAll() + * if it sees an event that notifies us of a completed + * registration. + */ + public class RegistrationEventCollector + implements RegistrationStateChangeListener + { + public List collectedNewStates = new LinkedList(); + + /** + * The method would simply register all received events so that they + * could be available for later inspection by the unit tests. In the + * case where a registraiton event notifying us of a completed + * registration is seen, the method would call notifyAll(). + * + * @param evt ProviderStatusChangeEvent the event describing the status + * change. + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + logger.debug("Received a RegistrationStateChangeEvent: " + evt); + + collectedNewStates.add(evt.getNewState()); + + if (evt.getNewState().equals(RegistrationState.REGISTERED)) + { + logger.debug("We're registered and will notify those who wait"); + synchronized (this) + { + notifyAll(); + } + } + } + + /** + * Blocks until an event notifying us of the awaited state change is + * received or until waitFor miliseconds pass (whichever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting + * for an event before simply bailing out. + */ + public void waitForEvent(long waitFor) + { + logger.trace("Waiting for a RegistrationStateChangeEvent "); + + synchronized (this) + { + if (collectedNewStates.contains(RegistrationState.REGISTERED)) + { + logger.trace("Event already received. " + + collectedNewStates); + return; + } + + try + { + wait(waitFor); + + if (collectedNewStates.size() > 0) + logger.trace( + "Received a RegistrationStateChangeEvent."); + else + logger.trace( + "No RegistrationStateChangeEvent received for " + + waitFor + "ms."); + + } + catch (InterruptedException ex) + { + logger.debug( + "Interrupted while waiting for a " + +"RegistrationStateChangeEvent" + , ex); + } + } + } + } + + /** + * A very simple straight forward implementation of a security authority + * that would always return the same password (the one specified upon + * construction) when asked for credentials. + */ + public class SecurityAuthorityImpl + implements SecurityAuthority + { + /** + * The password to return when asked for credentials + */ + private char[] passwd = null; + + /** + * Creates an instance of this class that would always return "passwd" + * when asked for credentials. + * + * @param passwd the password that this class should return when + * asked for credentials. + */ + public SecurityAuthorityImpl(char[] passwd) + { + this.passwd = passwd; + } + + /** + * Returns a Credentials object associated with the specified realm. + * <p> + * @param realm The realm that the credentials are needed for. + * @param defaultValues the values to propose the user by default + * @return The credentials associated with the specified realm or null + * if none could be obtained. + */ + public UserCredentials obtainCredentials(String realm, + UserCredentials defaultValues) + { + defaultValues.setPassword(passwd); + return defaultValues; + } + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/YahooProtocolProviderServiceLick.java b/test/net/java/sip/communicator/slick/protocol/yahoo/YahooProtocolProviderServiceLick.java new file mode 100644 index 0000000..49765b1 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/YahooProtocolProviderServiceLick.java @@ -0,0 +1,106 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import java.util.*; + +import org.osgi.framework.*; +import junit.framework.*; + +/** + * Yahoo specific testing for a Yahoo Protocol Provider Service implementation. + * The test suite registers two accounts for + * + * @author Damian Minkov + */ +public class YahooProtocolProviderServiceLick + extends TestSuite + implements BundleActivator +{ + /** + * The prefix used for property names containing settings for our first + * testing account. + */ + public static final String ACCOUNT_1_PREFIX + = "accounts.yahoo.account1."; + + /** + * The prefix used for property names containing settings for our second + * testing account. + */ + public static final String ACCOUNT_2_PREFIX + = "accounts.yahoo.account2."; + + /** + * The name of the property that indicates whether the user would like to + * only run the offline tests. + */ + public static final String DISABLE_ONLINE_TESTS_PROPERTY_NAME + = "accounts.yahoo.DISABLE_ONLINE_TESTING"; + + /** + * The name of the property the value of which is a formatted string that + * contains the contact list that. + */ + public static final String CONTACT_LIST_PROPERTY_NAME + = "accounts.yahoo.CONTACT_LIST"; + + /** + * Initializes and registers all tests that we'll run as a part of this + * slick. + * + * @param context a currently valid bundle context. + */ + public void start(BundleContext context) + { + setName("YahooProtocolProviderSlick"); + + Hashtable properties = new Hashtable(); + properties.put("service.pid", getName()); + + YahooSlickFixture.bc = context; + + // verify whether the user wants to avoid online testing + String offlineMode = System.getProperty( + DISABLE_ONLINE_TESTS_PROPERTY_NAME, null); + + if (offlineMode != null && offlineMode.equalsIgnoreCase("true")) + YahooSlickFixture.onlineTestingDisabled = true; + + + addTestSuite(TestAccountInstallation.class); + addTestSuite(TestProtocolProviderServiceYahooImpl.class); + + addTest(TestOperationSetPresence.suite()); + + //the following should only be run when we want online testing. + if(!YahooSlickFixture.onlineTestingDisabled) + { + addTest(TestOperationSetPersistentPresence.suite()); + + addTest(TestOperationSetBasicInstantMessaging.suite()); + + // Sending typing notifications doesn't work for now + //addTest(TestOperationSetTypingNotifications.suite()); + } + + addTest(TestAccountUninstallation.suite()); + addTestSuite(TestAccountUninstallationPersistence.class); + + context.registerService(getClass().getName(), this, properties); + } + + /** + * Prepares the slick for shutdown. + * + * @param context a currently valid bundle context. + */ + public void stop(BundleContext context) + { + + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/YahooSlickFixture.java b/test/net/java/sip/communicator/slick/protocol/yahoo/YahooSlickFixture.java new file mode 100644 index 0000000..1a62418 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/YahooSlickFixture.java @@ -0,0 +1,295 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.protocol.yahoo; + +import org.osgi.framework.*; +import junit.framework.*; +import net.java.sip.communicator.service.protocol.*; +import java.util.Map; +import java.util.*; + +/** + * Contains fields and methods used by most or all tests in the yahoo slick. + * + * @author Damian Minkov + */ +public class YahooSlickFixture + extends TestCase +{ + /** + * To be set by the slick itself upon activation. + */ + public static BundleContext bc = null; + + /** + * An osgi service reference for the protocol provider corresponding to our + * first testing account. + */ + public ServiceReference provider1ServiceRef = null; + + /** + * The protocol provider corresponding to our first testing account. + */ + public ProtocolProviderService provider1 = null; + + /** + * The user ID associated with testing account 1. + */ + public String userID1 = null; + + /** + * An osgi service reference for the protocol provider corresponding to our + * second testing account. + */ + public ServiceReference provider2ServiceRef = null; + + /** + * The protocol provider corresponding to our first testing account. + */ + public ProtocolProviderService provider2 = null; + + /** + * The user ID associated with testing account 2. + */ + public String userID2 = null; + + + /** + * The tested protocol provider factory. + */ + public ProtocolProviderFactory providerFactory = null; + + /** + * A reference to the bundle containing the tested pp implementation. This + * reference is set during the accoung uninstallation testing and used during + * the account uninstallation persistence testing. + */ + public static Bundle providerBundle = null; + + /** + * Indicates whether the user has requested for onlline tests not to be run. + * (e.g. due to lack of network connectivity or ... time constraints ;)). + */ + public static boolean onlineTestingDisabled = false; + + /** + * A Hashtable containing group names mapped against array lists of buddy + * screen names. This is a snapshot of the server stored buddy list for + * the account that is going to be used by the tested implementation. + * It is filled in by the tester agent who'd login with that account + * and initialise the ss contact list before the tested implementation has + * actually done so. + */ + public static Hashtable preInstalledBuddyList = null; + + + /** + * Initializes protocol provider references and whatever else there is to + * initialize. + * + * @throws java.lang.Exception in case we meet problems while retriving + * protocol providers through OSGI + */ + public void setUp() + throws Exception + { + // first obtain a reference to the provider factory + ServiceReference[] serRefs = null; + String osgiFilter = "(" + ProtocolProviderFactory.PROTOCOL + + "="+ProtocolNames.YAHOO+")"; + try{ + serRefs = bc.getServiceReferences( + ProtocolProviderFactory.class.getName(), osgiFilter); + } + catch (InvalidSyntaxException ex){ + //this really shouldhn't occur as the filter expression is static. + fail(osgiFilter + " is not a valid osgi filter"); + } + + assertTrue( + "Failed to find a provider factory service for protocol yahoo", + serRefs != null || serRefs.length > 0); + + //Keep the reference for later usage. + providerFactory = (ProtocolProviderFactory)bc.getService(serRefs[0]); + + userID1 = + System.getProperty( + YahooProtocolProviderServiceLick.ACCOUNT_1_PREFIX + + ProtocolProviderFactory.USER_ID); + + userID2 = + System.getProperty( + YahooProtocolProviderServiceLick.ACCOUNT_2_PREFIX + + ProtocolProviderFactory.USER_ID); + + //find the protocol providers exported for the two accounts + ServiceReference[] yahooProvider1Refs + = bc.getServiceReferences( + ProtocolProviderService.class.getName(), + "(&" + +"("+ProtocolProviderFactory.PROTOCOL+"="+ProtocolNames.YAHOO+")" + +"("+ProtocolProviderFactory.USER_ID+"=" + + userID1 +")" + +")"); + + //make sure we found a service + assertNotNull("No Protocol Provider was found for yahoo account1:" + + userID1 + , yahooProvider1Refs); + assertTrue("No Protocol Provider was found for yahoo account1:"+ userID1, + yahooProvider1Refs.length > 0); + + ServiceReference[] yahooProvider2Refs + = bc.getServiceReferences( + ProtocolProviderService.class.getName(), + "(&" + +"("+ProtocolProviderFactory.PROTOCOL+"="+ProtocolNames.YAHOO+")" + +"("+ProtocolProviderFactory.USER_ID+"=" + + userID2 +")" + +")"); + + //again make sure we found a service. + assertNotNull("No Protocol Provider was found for yahoo account2:" + + userID2 + , yahooProvider2Refs); + assertTrue("No Protocol Provider was found for yahoo account2:"+ userID2, + yahooProvider2Refs.length > 0); + + //save the service for other tests to use. + provider1ServiceRef = yahooProvider1Refs[0]; + provider1 = (ProtocolProviderService)bc.getService(provider1ServiceRef); + provider2ServiceRef = yahooProvider2Refs[0]; + provider2 = (ProtocolProviderService)bc.getService(provider2ServiceRef); + } + + /** + * Un get service references used in here. + */ + public void tearDown() + { + bc.ungetService(provider1ServiceRef); + bc.ungetService(provider2ServiceRef); + } + + /** + * Returns the bundle that has registered the protocol provider service + * implementation that we're currently testing. The method would go through + * all bundles currently installed in the framework and return the first + * one that exports the same protocol provider instance as the one we test + * in this slick. + * @param provider the provider whose bundle we're looking for. + * @return the Bundle that has registered the protocol provider service + * we're testing in the slick. + */ + public static Bundle findProtocolProviderBundle( + ProtocolProviderService provider) + { + Bundle[] bundles = bc.getBundles(); + + for (int i = 0; i < bundles.length; i++) + { + ServiceReference[] registeredServices + = bundles[i].getRegisteredServices(); + + if (registeredServices == null) + continue; + + for (int j = 0; j < registeredServices.length; j++) + { + Object service + = bc.getService(registeredServices[j]); + if (service == provider) + return bundles[i]; + } + } + + return null; + } + + public void clearProvidersLists() + throws Exception + { + Map supportedOperationSets1 = provider1.getSupportedOperationSets(); + + if ( supportedOperationSets1 == null + || supportedOperationSets1.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + +"this yahoo implementation. "); + + //get the operation set presence here. + OperationSetPersistentPresence opSetPersPresence1 = + (OperationSetPersistentPresence)supportedOperationSets1.get( + OperationSetPersistentPresence.class.getName()); + + //if still null then the implementation doesn't offer a presence + //operation set which is unacceptable for yahoo. + if (opSetPersPresence1 == null) + throw new NullPointerException( + "An implementation of the yahoo service must provide an " + + "implementation of at least the one of the Presence " + + "Operation Sets"); + + // lets do it once again for the second provider + Map supportedOperationSets2 = provider2.getSupportedOperationSets(); + + if (supportedOperationSets2 == null + || supportedOperationSets2.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + + "this yahoo implementation. "); + + //get the operation set presence here. + OperationSetPersistentPresence opSetPersPresence2 = + (OperationSetPersistentPresence) supportedOperationSets2.get( + OperationSetPersistentPresence.class.getName()); + + //if still null then the implementation doesn't offer a presence + //operation set which is unacceptable for yahoo. + if (opSetPersPresence2 == null) + throw new NullPointerException( + "An implementation of the yahoo service must provide an " + + "implementation of at least the one of the Presence " + + "Operation Sets"); + + ContactGroup rootGroup1 = opSetPersPresence1.getServerStoredContactListRoot(); + + // first delete the groups + Vector groupsToRemove = new Vector(); + Iterator iter = rootGroup1.subgroups(); + while (iter.hasNext()) + { + groupsToRemove.add(iter.next()); + } + + iter = groupsToRemove.iterator(); + while (iter.hasNext()) + { + ContactGroup item = (ContactGroup) iter.next(); + opSetPersPresence1.removeServerStoredContactGroup(item); + } + + ContactGroup rootGroup2 = opSetPersPresence2.getServerStoredContactListRoot(); + + // delete groups + groupsToRemove = new Vector(); + iter = rootGroup2.subgroups(); + while (iter.hasNext()) + { + groupsToRemove.add(iter.next()); + } + + iter = groupsToRemove.iterator(); + while (iter.hasNext()) + { + ContactGroup item = (ContactGroup) iter.next(); + opSetPersPresence2.removeServerStoredContactGroup(item); + } + + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/yahoo/yahoo.provider.slick.manifest.mf b/test/net/java/sip/communicator/slick/protocol/yahoo/yahoo.provider.slick.manifest.mf new file mode 100644 index 0000000..f8a0a77 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/yahoo/yahoo.provider.slick.manifest.mf @@ -0,0 +1,15 @@ +Bundle-Activator: net.java.sip.communicator.slick.protocol.yahoo.YahooProtocolProviderServiceLick +Bundle-Name: Yahoo Protocol Provider Service Leveraging Implementation Compatibility Kit +Bundle-Description: A Service Leveraging Implementation Compatibility Kit for the Yahoo implementation of the ProtocolProvider Service +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: net.java.sip.communicator.service.configuration, + net.java.sip.communicator.service.configuration.event, + junit.framework, + org.osgi.framework, + javax.net.ssl, + javax.xml.parsers, + net.java.sip.communicator.util, + net.java.sip.communicator.service.protocol, + net.java.sip.communicator.service.protocol.yahooconstants, + net.java.sip.communicator.service.protocol.event |