diff options
author | Damian Minkov <damencho@jitsi.org> | 2007-06-01 15:45:11 +0000 |
---|---|---|
committer | Damian Minkov <damencho@jitsi.org> | 2007-06-01 15:45:11 +0000 |
commit | 03262f0dd4b61a20d5e13832e70a10e8b3889b7b (patch) | |
tree | d03f92e081236e5307d8f6902b28ded71d665ae8 /src/net/java | |
parent | 2dfda989832e5daf4a72a39a2b41f23a533ef806 (diff) | |
download | jitsi-03262f0dd4b61a20d5e13832e70a10e8b3889b7b.zip jitsi-03262f0dd4b61a20d5e13832e70a10e8b3889b7b.tar.gz jitsi-03262f0dd4b61a20d5e13832e70a10e8b3889b7b.tar.bz2 |
RSS ProtocolProvider.
Diffstat (limited to 'src/net/java')
23 files changed, 5418 insertions, 0 deletions
diff --git a/src/net/java/sip/communicator/impl/protocol/rss/ContactGroupRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/ContactGroupRssImpl.java new file mode 100644 index 0000000..8546d93 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/ContactGroupRssImpl.java @@ -0,0 +1,620 @@ +/* + * 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.rss; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * A simple, straightforward implementation of a rss ContactGroup. Since + * the Rss protocol is not a real one, we simply store all group details + * in class fields. You should know that when implementing a real protocol, + * the contact group implementation would rather encapsulate group objects from + * the protocol stack and group property values should be returned by consulting + * the encapsulated object. + * + * @author Emil Ivov + */ +public class ContactGroupRssImpl + implements ContactGroup +{ + private static final Logger logger + = Logger.getLogger(ContactGroupRssImpl.class); + + /** + * The name of this Rss contact group. + */ + private String groupName = null; + + /** + * The list of this group's members. + */ + private Vector contacts = new Vector(); + + /** + * The list of sub groups belonging to this group. + */ + private Vector subGroups = new Vector(); + + /** + * The group that this group belongs to (or null if this is the root group). + */ + private ContactGroupRssImpl parentGroup = null; + + /** + * Determines whether this group is really in the contact list or whether + * it is here only temporarily and will be gone next time we restart. + */ + private boolean isPersistent = true; + + /** + * The protocol provider that created us. + */ + private ProtocolProviderServiceRssImpl parentProvider = null; + + /** + * Determines whether this group has been resolved on the server. + * Unresolved groups are groups that were available on previous runs and + * that the meta contact list has stored. During all next runs, when + * bootstrapping, the meta contact list would create these groups as + * unresolved. Once a protocol provider implementation confirms that the + * groups are still on the server, it would issue an event indicating that + * the groups are now resolved. + */ + private boolean isResolved = true; + + /** + * An id uniquely identifying the group. For many protocols this could be + * the group name itself. + */ + private String uid = null; + private static final String UID_SUFFIX = ".uid"; + + /** + * Creates a ContactGroupRssImpl with the specified name. + * + * @param groupName the name of the group. + * @param parentProvider the protocol provider that created this group. + */ + public ContactGroupRssImpl( + String groupName, + ProtocolProviderServiceRssImpl parentProvider) + { + this.groupName = groupName; + this.uid = groupName + UID_SUFFIX; + this.parentProvider = parentProvider; + } + + /** + * Determines whether the group may contain subgroups or not. + * + * @return always true in this implementation. + */ + public boolean canContainSubgroups() + { + 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 parentProvider; + } + + /** + * 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 contacts.iterator(); + } + + /** + * Adds the specified contact to this group. + * @param contactToAdd the ContactRssImpl to add to this group. + */ + public void addContact(ContactRssImpl contactToAdd) + { + this.contacts.add(contactToAdd); + contactToAdd.setParentGroup(this); + } + + /** + * 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 contacts.size(); + } + + /** + * Returns the number of subgroups contained by this + * <tt>ContactGroup</tt>. + * + * @return the number of subGroups currently added to this group. + */ + public int countSubgroups() + { + return subGroups.size(); + } + + /** + * Adds the specified contact group to the contained by this group. + * @param subgroup the ContactGroupRssImpl to add as a subgroup to this group. + */ + public void addSubgroup(ContactGroupRssImpl subgroup) + { + this.subGroups.add(subgroup); + subgroup.setParentGroup(this); + } + + /** + * Sets the group that is the new parent of this group + * @param parent ContactGroupRssImpl + */ + void setParentGroup(ContactGroupRssImpl parent) + { + this.parentGroup = parent; + } + + /** + * Returns the contact group that currently contains this group or null if + * this is the root contact group. + * @return the contact group that currently contains this group or null if + * this is the root contact group. + */ + public ContactGroup getParentContactGroup() + { + return this.parentGroup; + } + + /** + * Removes the specified contact group from the this group's subgroups. + * @param subgroup the ContactGroupRssImpl subgroup to remove. + */ + public void removeSubGroup(ContactGroupRssImpl subgroup) + { + this.subGroups.remove(subgroup); + subgroup.setParentGroup(null); + } + + + /** + * 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 (ContactRssImpl)contacts.get(index); + } + + /** + * Returns the group that is parent of the specified rssGroup or null + * if no parent was found. + * @param rssGroup the group whose parent we're looking for. + * @return the ContactGroupRssImpl instance that rssGroup + * belongs to or null if no parent was found. + */ + public ContactGroupRssImpl findGroupParent( + ContactGroupRssImpl rssGroup) + { + if ( subGroups.contains(rssGroup) ) + return this; + + Iterator subGroupsIter = subgroups(); + while (subGroupsIter.hasNext()) + { + ContactGroupRssImpl subgroup + = (ContactGroupRssImpl) subGroupsIter.next(); + + ContactGroupRssImpl parent + = subgroup.findGroupParent(rssGroup); + + if(parent != null) + return parent; + } + return null; + } + + /** + * Returns the group that is parent of the specified rssContact or + * null if no parent was found. + * + * @param rssContact the contact whose parent we're looking for. + * @return the ContactGroupRssImpl instance that rssContact + * belongs to or <tt>null</tt> if no parent was found. + */ + public ContactGroupRssImpl findContactParent( + ContactRssImpl rssContact) + { + if ( contacts.contains(rssContact) ) + return this; + + Iterator subGroupsIter = subgroups(); + while (subGroupsIter.hasNext()) + { + ContactGroupRssImpl subgroup + = (ContactGroupRssImpl) subGroupsIter.next(); + + ContactGroupRssImpl parent + = subgroup.findContactParent(rssContact); + + if(parent != null) + return parent; + } + 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) + { + Iterator contactsIter = contacts(); + while (contactsIter.hasNext()) + { + ContactRssImpl contact = (ContactRssImpl) contactsIter.next(); + if (contact.getAddress().equals(id)) + return contact; + + } + 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 (ContactGroup)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 groupsIter = subgroups(); + while (groupsIter.hasNext()) + { + ContactGroupRssImpl contactGroup + = (ContactGroupRssImpl) groupsIter.next(); + if (contactGroup.getGroupName().equals(groupName)) + return contactGroup; + + } + return null; + + } + + /** + * Returns the name of this group. + * + * @return a String containing the name of this group. + */ + public String getGroupName() + { + return this.groupName; + } + + /** + * Sets this group a new name. + * @param newGrpName a String containing the new name of this group. + */ + public void setGroupName(String newGrpName) + { + this.groupName = newGrpName; + } + + /** + * 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(); + } + + /** + * Removes the specified contact from this group. + * @param contact the ContactRssImpl to remove from this group + */ + public void removeContact(ContactRssImpl contact) + { + this.contacts.remove(contact); + } + + /** + * Returns the contact with the specified id or null if no such contact + * exists. + * @param id the id of the contact we're looking for. + * @return ContactRssImpl + */ + public ContactRssImpl findContactByID(String id) + { + //first go through the contacts that are direct children. + Iterator contactsIter = contacts(); + + while(contactsIter.hasNext()) + { + ContactRssImpl mContact = (ContactRssImpl)contactsIter.next(); + + if( mContact.getAddress().equals(id) ) + return mContact; + } + + //if we didn't find it here, let's try in the subougroups + Iterator groupsIter = subgroups(); + + while( groupsIter.hasNext() ) + { + ContactGroupRssImpl mGroup = (ContactGroupRssImpl)groupsIter.next(); + + ContactRssImpl mContact = mGroup.findContactByID(id); + + if (mContact != null) + return mContact; + } + + return null; + } + + + /** + * Returns a String representation of this group and the contacts it + * contains (may turn out to be a relatively long string). + * @return a String representing this group and its child contacts. + */ + public String toString() + { + + StringBuffer buff = new StringBuffer(getGroupName()); + buff.append(".subGroups=" + countSubgroups() + ":\n"); + + Iterator subGroups = subgroups(); + while (subGroups.hasNext()) + { + ContactGroupRssImpl group = (ContactGroupRssImpl)subGroups.next(); + buff.append(group.toString()); + if (subGroups.hasNext()) + buff.append("\n"); + } + + buff.append("\nChildContacts="+countContacts()+":["); + + Iterator contacts = contacts(); + while (contacts.hasNext()) + { + ContactRssImpl contact = (ContactRssImpl) contacts.next(); + buff.append(contact.toString()); + if(contacts.hasNext()) + buff.append(", "); + } + return buff.append("]").toString(); + } + + public Vector getRssURLList(Vector rssURLList){ + //private Vector rssURLList; + //StringBuffer buff = new StringBuffer(getGroupName()); + //buff.append(".subGroups=" + countSubgroups() + ":\n"); + + Iterator subGroups = subgroups(); + while (subGroups.hasNext()) + { + ContactGroupRssImpl group = (ContactGroupRssImpl)subGroups.next(); + //buff.append( + group.getRssURLList(rssURLList); + // if (subGroups.hasNext()) + // buff.append("\n"); + } + + //buff.append("\nChildContacts="+countContacts()+":["); + + Iterator contacts = contacts(); + while (contacts.hasNext()) + { + ContactRssImpl contact = (ContactRssImpl) contacts.next(); + //buff.append(contact.getDisplayName()); + rssURLList.addElement(contact); + //if(contacts.hasNext()) + // buff.append(", "); + } + //return buff.append("]").toString(); + return rssURLList; + } + + /** + * Specifies whether or not this contact group is being stored by the server. + * Non persistent contact groups 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 and the contact that we + * associated with that user is placed in a non persistent group. Non + * persistent contact groups 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. + * + * @param isPersistent true if the contact group is to be persistent and + * false otherwise. + */ + public void setPersistent(boolean isPersistent) + { + this.isPersistent = isPersistent; + } + + /** + * 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 isPersistent; + } + + /** + * 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 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; + } + + /** + * Makes the group resolved or unresolved. + * + * @param resolved true to make the group resolved; false to + * make it unresolved + */ + public void setResolved(boolean resolved) + { + this.isResolved = resolved; + } + + /** + * Returns a <tt>String</tt> that uniquely represnets the group inside + * the current protocol. The string MUST be persistent (it must not change + * across connections or runs of the application). In many cases (Jabber, + * ICQ) the string may match the name of the group as these protocols + * only allow a single level of contact groups and there is no danger of + * having the same name twice in the same contact list. Other protocols + * (no examples come to mind but that doesn't bother me ;) ) may be + * supporting mutilple levels of grooups so it might be possible for group + * A and group B to both contain groups named C. In such cases the + * implementation must find a way to return a unique identifier in this + * method and this UID should never change for a given group. + * + * @return a String representing this group in a unique and persistent + * way. + */ + public String getUID() + { + return uid; + } + + /** + * Ugly but tricky conversion method. + * @param uid the uid we'd like to get a name from + * @return the name of the group with the specified <tt>uid</tt>. + */ + static String createNameFromUID(String uid) + { + return uid.substring(0, uid.length() - (UID_SUFFIX.length())); + } + + /** + * Indicates whether some other object is "equal to" this one which in terms + * of contact groups translates to having the equal names and matching + * subgroups and child contacts. The resolved status of contactgroups and + * contacts is deliberately ignored so that groups and/or contacts would + * be assumed equal even if it differs. + * <p> + * @param obj the reference object with which to compare. + * @return <code>true</code> if this contact group has the equal child + * contacts and subgroups to those of the <code>obj</code> argument. + */ + public boolean equals(Object obj) + { + if(obj == null + || !(obj instanceof ContactGroupRssImpl)) + return false; + + ContactGroupRssImpl rssGroup + = (ContactGroupRssImpl)obj; + + if( ! rssGroup.getGroupName().equals(getGroupName()) + || ! rssGroup.getUID().equals(getUID()) + || rssGroup.countContacts() != countContacts() + || rssGroup.countSubgroups() != countSubgroups()) + return false; + + //traverse child contacts + Iterator theirContacts = rssGroup.contacts(); + + while(theirContacts.hasNext()) + { + ContactRssImpl theirContact + = (ContactRssImpl)theirContacts.next(); + + ContactRssImpl ourContact + = (ContactRssImpl)getContact(theirContact.getAddress()); + + if(ourContact == null + || !ourContact.equals(theirContact)) + return false; + } + + //traverse subgroups + Iterator theirSubgroups = rssGroup.subgroups(); + + while(theirSubgroups.hasNext()) + { + ContactGroupRssImpl theirSubgroup + = (ContactGroupRssImpl)theirSubgroups.next(); + + ContactGroupRssImpl ourSubgroup + = (ContactGroupRssImpl)getGroup( + theirSubgroup.getGroupName()); + + if(ourSubgroup == null + || !ourSubgroup.equals(theirSubgroup)) + return false; + } + + return true; + } +} + diff --git a/src/net/java/sip/communicator/impl/protocol/rss/ContactRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/ContactRssImpl.java new file mode 100644 index 0000000..ae9428c --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/ContactRssImpl.java @@ -0,0 +1,428 @@ +/* + * 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.rss; + +import java.util.*; +import java.text.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * An implementation of a rss Contact. + * + * @author Jean-Albert Vescovo + */ +public class ContactRssImpl + implements Contact +{ + private String lastDate = null; + private Date date = null; + private String nickName = null; + + private static final Logger logger + = Logger.getLogger(ContactRssImpl.class); + + private static SimpleDateFormat DATE_FORMATTER = + new SimpleDateFormat("yyyy.MM.dd-HH:mm:ss"); + + /** + * The id of the contact. + */ + private String contactID = null; + + /** + * The provider that created us. + */ + private ProtocolProviderServiceRssImpl parentProvider = null; + + /** + * The group that belong to. + */ + private ContactGroupRssImpl parentGroup = null; + + /** + * The presence status of the contact. + */ + private PresenceStatus presenceStatus = RssStatusEnum.ONLINE; + + /** + * Determines whether this contact is persistent, i.e. member of the contact + * list or whether it is here only temporarily. + */ + private boolean isPersistent = true; + + /** + * Determines whether the contact has been resolved (i.e. we have a + * confirmation that it is still on the server contact list). + */ + private boolean isResolved = false; + + /** + * Creates an instance of a meta contact with the specified string used + * as a name and identifier. + * + * @param id the identifier of this contact (also used as a name). + * @param parentProvider the provider that created us. + */ + public ContactRssImpl( + String id, + ProtocolProviderServiceRssImpl parentProvider) + { + this.contactID = id; + this.parentProvider = parentProvider; + } + + /** + * This method is only called when the contact is added to a new + * <tt>ContactGroupRssImpl</tt> by the + * <tt>ContactGroupRssImpl</tt> itself. + * + * @param newParentGroup the <tt>ContactGroupRssImpl</tt> that is now + * parent of this <tt>ContactRssImpl</tt> + */ + void setParentGroup(ContactGroupRssImpl newParentGroup) + { + this.parentGroup = newParentGroup; + } + + /** + * Returns a String that can be used for identifying the contact. + * + * @return a String id representing and uniquely identifying the contact. + */ + public String getAddress() + { + return contactID; + } + + /** + * Returns a String that could be used by any user interacting modules + * for referring to this contact. + * + * @return a String that can be used for referring to this contact when + * interacting with the user. + */ + public String getDisplayName() + { + if(nickName == null) return contactID; + else return nickName; + } + + public void setDisplayName(String nickName){ + this.nickName = nickName; + } + + /** + * Returns a Date corresponding to the date of the last query + * on this rss contact. + * + * @return a Date in order to compare with a new one obtained via + * a query on the feed. + */ + public Date getDate() + { + return this.date; + } + + /** + * This method is only called when a new date is found after a query + * on the feed corresponding to this contact + * + * @param date the <tt>Date</tt> that is now + * the last update date of the <tt>ContactRssImpl</tt> + */ + public void setDate(Date date) + { + this.date = date; + this.lastDate = convertDateToString(this.date); + } + + /** + * Updating the lastDate in String format of the contact + * + * @param lastDate the <tt>String</tt> that is now + * the last update date of the <tt>ContactRssImpl</tt> + */ + public void setLastDate(String lastDate) + { + this.lastDate = lastDate; + } + + /** + * Returns a String corresponding to the date of the last query + * on this rss contact. + * + * @return a String representing a Date in order to compare with + * a new one obtained via a query on the feed. + */ + public String getLastDate() + { + return this.lastDate; + } + + /** + * Returns a String corresponding to a date after a conversion + * from a Date + * + * @param date the date + * @return a String which is placed in the lastDate variable of the + * present contact + */ + private String convertDateToString(Date date) + { + return DATE_FORMATTER.format(date); + } + + /** + * This method is called when a the contact is restored and a + * previous saved lastDate is found as persistent-data: this + * data is in a String format, and this method convert it into + * a Date usable by the protocol. + * @param lastDate date as String + */ + private void convertStringToDate(String lastDate) + { + try + { + this.date = DATE_FORMATTER.parse(lastDate); + } + catch(ParseException ex) + { + logger.error("Cannot parse Date", ex); + } + } + + /** + * Returns an array of String corresponding to a date bursted in multiple + * fields as this: + * ddd mmm DD HH:mm:ss ZZZZ YYYY + * + * @return an Array of String + */ + private String[] getToken(String param1, String param2) + { + int i = 0; + String data[] = new String[8]; + StringTokenizer tmp = new StringTokenizer(param1, param2); + + while(tmp.hasMoreTokens()) + { + data[i] = tmp.nextToken(); + i++; + } + return data; + } + + /** + * Returns a byte array containing an image (most often a photo or an + * avatar) that the contact uses as a representation. + * + * @return byte[] an image representing the contact. + */ + public byte[] getImage() + { + return null; + } + + /** + * Returns the status of the contact. + * + * @return RssStatusEnum.STATUS. + */ + public PresenceStatus getPresenceStatus() + { + return this.presenceStatus; + } + + /** + * Sets <tt>rssPresenceStatus</tt> as the PresenceStatus that this + * contact is currently in. + * @param rssPresenceStatus the <tt>RssPresenceStatus</tt> + * currently valid for this contact. + */ + public void setPresenceStatus(PresenceStatus rssPresenceStatus) + { + this.presenceStatus = rssPresenceStatus; + } + + /** + * Returns a reference to the protocol provider that created the contact. + * + * @return a refererence to an instance of the ProtocolProviderService + */ + public ProtocolProviderService getProtocolProvider() + { + return parentProvider; + } + + /** + * Determines whether or not this contact represents our own identity. + * + * @return true in case this is a contact that represents ourselves and + * false otherwise. + */ + public boolean isLocal() + { + return false; + } + + /** + * Returns the group that contains this contact. + * @return a reference to the <tt>ContactGroupRssImpl</tt> that + * contains this contact. + */ + public ContactGroup getParentContactGroup() + { + return this.parentGroup; + } + + /** + * 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("ContactRssImpl[ DisplayName=") + .append(getDisplayName()).append("]"); + + return buff.toString(); + } + + /** + * 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 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. + * + * @param isPersistent true if the contact is persistent and false + * otherwise. + */ + public void setPersistent(boolean isPersistent) + { + this.isPersistent = isPersistent; + } + + /** + * 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() + { + // to store data only when lastDate is set + if(lastDate != null) + return "lastDate=" + lastDate + ";"; + else + return null; + } + + public void setPersistentData(String persistentData) + { + if(persistentData == null) + { + return; + } + + StringTokenizer dataToks = new StringTokenizer(persistentData, ";"); + while(dataToks.hasMoreTokens()) + { + String data[] = dataToks.nextToken().split("="); + if(data[0].equals("lastDate") && data.length > 1) + { + this.lastDate = data[1]; + convertStringToDate(this.lastDate); + } + } + } + + /** + * 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; + } + + /** + * Makes the contact resolved or unresolved. + * + * @param resolved true to make the contact resolved; false to + * make it unresolved + */ + public void setResolved(boolean resolved) + { + this.isResolved = resolved; + } + + /** + * Indicates whether some other object is "equal to" this one which in terms + * of contacts translates to having equal ids. The resolved status of the + * contacts deliberately ignored so that contacts would be declared equal + * even if it differs. + * <p> + * @param obj the reference object with which to compare. + * @return <code>true</code> if this contact has the same id as that of the + * <code>obj</code> argument. + */ + public boolean equals(Object obj) + { + if (obj == null + || ! (obj instanceof ContactRssImpl)) + return false; + + ContactRssImpl rssContact = (ContactRssImpl) obj; + + return this.getAddress().equals(rssContact.getAddress()); + } + + + /** + * Returns the persistent presence operation set that this contact belongs + * to. + * + * @return the <tt>OperationSetPersistentPresenceRssImpl</tt> that + * this contact belongs to. + */ + public OperationSetPersistentPresenceRssImpl + getParentPresenceOperationSet() + { + return (OperationSetPersistentPresenceRssImpl)parentProvider + .getOperationSet(OperationSetPersistentPresence.class); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/MessageRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/MessageRssImpl.java new file mode 100644 index 0000000..1071bdf --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/MessageRssImpl.java @@ -0,0 +1,137 @@ +/* + * 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.rss; + +import net.java.sip.communicator.service.protocol.*; + +/** + * Very simple message implementation for the Rss protocol. + * + * @author Emil Ivov + */ +public class MessageRssImpl + implements Message +{ + /** + * The actual message content. + */ + private String textContent = null; + + /** + * The content type of the message. (text/plain if null) + */ + private String contentType = null; + + /** + * The message encoding. (UTF8 if null). + */ + private String contentEncoding = null; + + /** + * A String uniquely identifying the message + */ + private String messageUID = null; + + /** + * The subject of the message. (most often is null) + */ + private String subject = null; + + /** + * Creates a message instance according to the specified parameters. + * + * @param content the message body + * @param contentType message content type or null for text/plain + * @param contentEncoding message encoding or null for UTF8 + * @param subject the subject of the message or null for no subject. + */ + public MessageRssImpl(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 message body. + * + * @return the message content. + */ + public String getContent() + { + return textContent; + } + + /** + * Returns the type of the content of this message. + * + * @return the type of the content of this message. + */ + public String getContentType() + { + return contentType; + } + + /** + * Returns the encoding used for the message content. + * + * @return the encoding of the message body. + */ + public String getEncoding() + { + return contentEncoding; + } + + /** + * A string uniquely identifying the message. + * + * @return a <tt>String</tt> uniquely identifying the message. + */ + public String getMessageUID() + { + return messageUID; + } + + /** + * Returns the message body in a binary form. + * + * @return a <tt>byte[]</tt> representation of the message body. + */ + public byte[] getRawData() + { + return getContent().getBytes(); + } + + /** + * Return the length of this message. + * + * @return the length of this message. + */ + public int getSize() + { + return getContent().length(); + } + + /** + * Returns the message subject. + * + * @return the message subject. + */ + public String getSubject() + { + return subject; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/OperationSetBasicInstantMessagingRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/OperationSetBasicInstantMessagingRssImpl.java new file mode 100644 index 0000000..ef76a7a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/OperationSetBasicInstantMessagingRssImpl.java @@ -0,0 +1,436 @@ +/* + * 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.rss; + +import java.util.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; + +import java.awt.*; +import java.awt.event.*; + +/** + * Instant messaging functionalites for the Rss protocol. + * + * @author Jean-Albert Vescovo + */ +public class OperationSetBasicInstantMessagingRssImpl + implements OperationSetBasicInstantMessaging +{ + /** + * Currently registered message listeners. + */ + private Vector messageListeners = new Vector(); + + /** + * The currently valid persistent presence operation set.. + */ + private OperationSetPersistentPresenceRssImpl opSetPersPresence = null; + + /** + * The protocol provider that created us. + */ + private ProtocolProviderServiceRssImpl parentProvider = null; + + /** + * The timer used in order to refresh one or more rss feeds + */ + private Timer timer = null; + + /** + * The value corresponding to the time in ms + * of the rss refreshing period (here 10min) + */ + final int PERIOD_REFRESH_RSS = 600000; + + /** + * Creates an instance of this operation set keeping a reference to the + * parent protocol provider and presence operation set. + * + * @param provider The provider instance that creates us. + * @param opSetPersPresence the currently valid + * <tt>OperationSetPersistentPresenceRssImpl</tt> instance. + */ + public OperationSetBasicInstantMessagingRssImpl( + ProtocolProviderServiceRssImpl provider, + OperationSetPersistentPresenceRssImpl opSetPersPresence) + { + this.opSetPersPresence = opSetPersPresence; + this.parentProvider = provider; + } + + /** + * Registers 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) + { + if(!messageListeners.contains(listener)) + messageListeners.add(listener); + } + + /** + * 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 MessageRssImpl(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 MessageRssImpl(messageText, DEFAULT_MIME_TYPE + , DEFAULT_MIME_ENCODING, null); + } + + /** + * 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) + { + messageListeners.remove(listener); + } + + /** + * Looks for a RSS feed specified as contact + * + * @param rssContact the <tt>contact</tt> to send query + * @param newContact the <tt>boolean</tt> to now if it's a new feed/contact + * @param aloneUpdate the <tt>boolean</tt> to know if it's + * a query just for one feed/contact + */ + private void submitRssQuery(ContactRssImpl rssContact, + boolean newContact, + boolean aloneUpdate) + { + Message msg; + boolean newName = false; + boolean newDate = false; + boolean update = false; + Date lastQueryDate = null; + String newDisplayName = new String(); + String oldDisplayName = new String(); + + //we instantiate a new RssFeedReader which will contain the feed retrieved + RssFeedReader rssFeed = new RssFeedReader(rssContact.getAddress()); + + //we parse the feed/contact + rssFeed.recupFlux(); + + if(rssFeed.getFeed() == null) + { + msg = createMessage("No RSS feed available at URL "+ rssContact.getAddress()); + }else + { + //we recover the feed's old name + if(newContact) + oldDisplayName = rssContact.getDisplayName(); + else + oldDisplayName = rssFeed.getTitle(); + + //we change the contact's displayName according to the feed's title + newDisplayName = rssFeed.getTitle(); + if(!(newDisplayName.equals(oldDisplayName))) + { + newName = true; + } + rssContact.setDisplayName(newDisplayName); + + //Looking for a date representing the last item retrieved on this feed + //we look after a date saving in the contact's parameters (i.e. in the + // file contactlist.xml) + if(rssContact.getDate() != null) + lastQueryDate = rssContact.getDate(); + + //we create the message containing the new items retrieved + msg = createMessage(rssFeed.getPrintedFeed(lastQueryDate)); + + //if a newer date is avalaible for the current feed/contact looking the + // date of each item of the feed retrieved, we update this date + if(rssFeed.getUltimateItemDate() != null) + { + if(lastQueryDate != null) + { + if(rssFeed.getUltimateItemDate().compareTo(lastQueryDate)>0) + { + rssContact.setDate(rssFeed.getUltimateItemDate()); + newDate = true; + update = true; + } + } + else + { + rssContact.setDate(rssFeed.getUltimateItemDate()); + newDate = true; + update = true; + } + } + else + update = true; + + //if we have a new date or a new name on this feed/contact, we fire that + // the contact has his properties modified in order to save it + if(newName || newDate) + this.opSetPersPresence.fireContactPropertyChangeEvent( + ContactPropertyChangeEvent. + PROPERTY_DISPLAY_NAME, rssContact, + oldDisplayName, newDisplayName); + } + + //if the feed has been updated or if the user made a request on a specific + //feed/contact, we fire a new message containing the new items to the user + if(update || aloneUpdate) + fireMessageReceived(msg, rssContact); + } + + /** + * To refresh all rss feeds registered as contacts + */ + public void refreshRssFeed() + { + Vector rssContactList = new Vector(); + rssContactList = opSetPersPresence.getContactListRoot(). + getRssURLList(rssContactList); + Iterator rssContact = rssContactList.iterator(); + while(rssContact.hasNext()) + { + submitRssQuery((ContactRssImpl)rssContact.next(), false, false); + } + } + + /** + * To refresh a specific rss feed specified as param + * + * @param rssURL the <tt>contact</tt> to be refreshed + * @param newContact + * @param aloneUpdate + */ + public void refreshRssFeed( ContactRssImpl rssURL, + boolean newContact, + boolean aloneUpdate) + { + submitRssQuery(rssURL, newContact, aloneUpdate); + } + + /** + * Creating the timer permitting the refresh of rss feeds + */ + public void createTimer() + { + RssTimerRefreshFeed refresh = new RssTimerRefreshFeed(this); + this.timer = new Timer(); + this.timer.scheduleAtFixedRate(refresh, 0, PERIOD_REFRESH_RSS); + } + + /** + * Cancel the timer if the user switch to the OFFLINE status + */ + public void stopTimer(){ + this.timer.cancel(); + } + + /** + * Retrieve the feeds for a new Rss Feed just added as persistent contact + * + * @param contact the <tt>Contact</tt> added + */ + public void newContact(ContactRssImpl contact) + { + RssThread rssThr = new RssThread(this,contact, true, true); + } + + /** + * 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 IllegalStateException if the underlying ICQ stack is not + * registered and initialized. + * @throws IllegalArgumentException if <tt>to</tt> is not an instance + * belonging to the underlying implementation. + */ + public void sendInstantMessage(Contact to, Message message) + throws IllegalStateException, + IllegalArgumentException + { + if( !(to instanceof ContactRssImpl) ) + throw new IllegalArgumentException( + "The specified contact is not a Rss contact." + + to); + + MessageDeliveredEvent msgDeliveredEvt + = new MessageDeliveredEvent(message, to, new Date()); + + //refresh the present rssFeed "to" + fireMessageDelivered(message,to); + RssThread rssThr = new RssThread(this, (ContactRssImpl)to, false, true); + } + + /** + * In case the to the <tt>to</tt> Contact corresponds to another rss + * protocol provider registered with SIP Communicator, we deliver + * the message to them, in case the <tt>to</tt> Contact represents us, we + * fire a <tt>MessageReceivedEvent</tt>, and if <tt>to</tt> is simply + * a contact in our contact list, then we simply echo the message. + * + * @param message the <tt>Message</tt> the message to deliver. + * @param to the <tt>Contact</tt> that we should deliver the message to. + */ + private void deliverMessage(Message message, ContactRssImpl to) + { + String userID = to.getAddress(); + + //if the user id is owr own id, then this message is being routed to us + //from another instance of the rss provider. + if (userID.equals(this.parentProvider.getAccountID().getUserID())) + { + //check who is the provider sending the message + String sourceUserID + = to.getProtocolProvider().getAccountID().getUserID(); + + //check whether they are in our contact list + Contact from = opSetPersPresence.findContactByID(sourceUserID); + + + //and if not - add them there as volatile. + if(from == null) + { + from = opSetPersPresence.createVolatileContact(sourceUserID); + } + + //and now fire the message received event. + fireMessageReceived(message, from); + } + else + { + //if userID is not our own, try an check whether another provider + //has that id and if yes - deliver the message to them. + ProtocolProviderServiceRssImpl rssProvider + = this.opSetPersPresence.findProviderForRssUserID(userID); + if(rssProvider != null) + { + OperationSetBasicInstantMessagingRssImpl opSetIM + = (OperationSetBasicInstantMessagingRssImpl) + rssProvider.getOperationSet( + OperationSetBasicInstantMessaging.class); + opSetIM.deliverMessage(message, to); + } + else + { + //if we got here then "to" is simply someone in our contact + //list so let's just echo the message. + fireMessageReceived(message, to); + } + } + } + + /** + * Notifies all registered message listeners that a message has been + * delivered successfully to its addressee.. + * + * @param message the <tt>Message</tt> that has been delivered. + * @param to the <tt>Contact</tt> that <tt>message</tt> was delivered to. + */ + private void fireMessageDelivered(Message message, Contact to) + { + MessageDeliveredEvent evt + = new MessageDeliveredEvent(message, to, new Date()); + + Iterator listeners = null; + synchronized (messageListeners) + { + listeners = new ArrayList(messageListeners).iterator(); + } + + while (listeners.hasNext()) + { + MessageListener listener + = (MessageListener) listeners.next(); + + listener.messageDelivered(evt); + } + } + + /** + * Notifies all registered message listeners that a message has been + * received. + * + * @param message the <tt>Message</tt> that has been received. + * @param from the <tt>Contact</tt> that <tt>message</tt> was received from. + */ + private void fireMessageReceived(Message message, Contact from) + { + MessageReceivedEvent evt + = new MessageReceivedEvent(message, from, new Date()); + + Iterator listeners = null; + synchronized (messageListeners) + { + listeners = new ArrayList(messageListeners).iterator(); + } + + while (listeners.hasNext()) + { + MessageListener listener + = (MessageListener) listeners.next(); + + listener.messageReceived(evt); + } + } + + /** + * 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; + } + + public ProtocolProviderServiceRssImpl getParentProvider(){ + return this.parentProvider; + } + + public OperationSetPersistentPresenceRssImpl getOpSetPersPresence(){ + return this.opSetPersPresence; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/OperationSetPersistentPresenceRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/OperationSetPersistentPresenceRssImpl.java new file mode 100644 index 0000000..e9ab143 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/OperationSetPersistentPresenceRssImpl.java @@ -0,0 +1,1357 @@ +/* + * 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.rss; + +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 org.osgi.framework.*; + +/** + * A Rss implementation of a persistent presence operation set. In order + * to simulate server persistence, this operation set would simply accept all + * unresolved contacts and resolve them immediately. A real world protocol + * implementation would save it on a server using methods provided by the + * protocol stack. + * + * @author Emil Ivov/Jean-Albert Vescovo + */ +public class OperationSetPersistentPresenceRssImpl + implements OperationSetPersistentPresence +{ + private static final Logger logger = + Logger.getLogger(OperationSetPersistentPresenceRssImpl.class); + /** + * A list of listeners registered for <tt>SubscriptionEvent</tt>s. + */ + private Vector subscriptionListeners = new Vector(); + + /** + * A list of listeners registered for <tt>ServerStoredGroupChangeEvent</tt>s. + */ + private Vector serverStoredGroupListeners = new Vector(); + + /** + * A list of listeners registered for + * <tt>ProviderPresenceStatusChangeEvent</tt>s. + */ + private Vector providerPresenceStatusListeners = new Vector(); + + /** + * A list of listeneres registered for + * <tt>ContactPresenceStatusChangeEvent</tt>s. + */ + private Vector contactPresenceStatusListeners = new Vector(); + + /** + * The root of the rss contact list. + */ + private ContactGroupRssImpl contactListRoot = null; + + /** + * The provider that created us. + */ + private ProtocolProviderServiceRssImpl parentProvider = null; + + /** + * The currently active status message. + */ + private String statusMessage = "Default Status Message"; + + /** + * Our default presence status. + */ + private PresenceStatus presenceStatus = RssStatusEnum.ONLINE; + + /** + * The <tt>AuthorizationHandler</tt> instance that we'd have to transmit + * authorization requests to for approval. + */ + private AuthorizationHandler authorizationHandler = null; + + /** + * Creates an instance of this operation set keeping a reference to the + * specified parent <tt>provider</tt>. + * @param provider the ProtocolProviderServiceRssImpl instance that + * created us. + */ + public OperationSetPersistentPresenceRssImpl( + ProtocolProviderServiceRssImpl provider) + { + this.parentProvider = provider; + contactListRoot = new ContactGroupRssImpl("RootGroup", provider); + + //add our unregistration listener + parentProvider.addRegistrationStateChangeListener( + new UnregistrationListener()); + } + + /** + * Rss implementation of the corresponding ProtocolProviderService + * method. + * + * @param listener a dummy param. + */ + public void addContactPresenceStatusListener( + ContactPresenceStatusListener listener) + { + synchronized(contactPresenceStatusListeners) + { + if (!contactPresenceStatusListeners.contains(listener)) + contactPresenceStatusListeners.add(listener); + } + } + + /** + * Notifies all registered listeners of the new event. + * + * @param source the contact that has caused the event. + * @param parentGroup the group that contains the source contact. + * @param oldValue the status that the source contact detained before + * changing it. + */ + public void fireContactPresenceStatusChangeEvent(ContactRssImpl source, + ContactGroup parentGroup, + PresenceStatus oldValue) + { + ContactPresenceStatusChangeEvent evt + = new ContactPresenceStatusChangeEvent(source, parentProvider + , parentGroup, oldValue, source.getPresenceStatus()); + + Iterator listeners = null; + synchronized(contactPresenceStatusListeners) + { + listeners = new ArrayList(contactPresenceStatusListeners).iterator(); + } + + + while(listeners.hasNext()) + { + ContactPresenceStatusListener listener + = (ContactPresenceStatusListener)listeners.next(); + + listener.contactPresenceStatusChanged(evt); + } + } + + /** + * Notify all subscription listeners of the corresponding contact property + * change event. + * + * @param eventID the String ID of the event to dispatch + * @param sourceContact the ContactRssImpl instance that this event is + * pertaining to. + * @param oldValue the value that the changed property had before the change + * occurred. + * @param newValue the value that the changed property currently has (after + * the change has occurred). + */ + void fireContactPropertyChangeEvent( String eventID, + ContactRssImpl sourceContact, + Object oldValue, + Object newValue) + { + ContactPropertyChangeEvent evt = + new ContactPropertyChangeEvent(sourceContact, eventID + , oldValue, newValue); + + logger.debug("Dispatching a Contact Property Change 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.contactModified(evt); + } + } + + /** + * Notifies all registered listeners of the new event. + * + * @param source the contact that has caused the event. + * @param parentGroup the group that contains the source contact. + * @param eventID an identifier of the event to dispatch. + */ + public void fireSubscriptionEvent(ContactRssImpl source, + ContactGroup parentGroup, + int eventID) + { + SubscriptionEvent evt = new SubscriptionEvent(source + , this.parentProvider + , parentGroup + , eventID); + + Iterator listeners = null; + synchronized (subscriptionListeners) + { + listeners = new ArrayList(subscriptionListeners).iterator(); + } + + while (listeners.hasNext()) + { + SubscriptionListener listener + = (SubscriptionListener) listeners.next(); + + if(eventID == SubscriptionEvent.SUBSCRIPTION_CREATED) + { + listener.subscriptionCreated(evt); + } + else if (eventID == SubscriptionEvent.SUBSCRIPTION_FAILED) + { + listener.subscriptionFailed(evt); + } + else if (eventID == SubscriptionEvent.SUBSCRIPTION_REMOVED) + { + listener.subscriptionRemoved(evt); + } + } + } + + /** + * Notifies all registered listeners of the new event. + * + * @param source the contact that has been moved.. + * @param oldParent the group where the contact was located before being + * moved. + * @param newParent the group where the contact has been moved. + */ + public void fireSubscriptionMovedEvent(Contact source, + ContactGroup oldParent, + ContactGroup newParent) + { + SubscriptionMovedEvent evt = new SubscriptionMovedEvent(source + , this.parentProvider + , oldParent + , newParent); + + Iterator listeners = null; + synchronized (subscriptionListeners) + { + listeners = new ArrayList(subscriptionListeners).iterator(); + } + + while (listeners.hasNext()) + { + SubscriptionListener listener + = (SubscriptionListener) listeners.next(); + + listener.subscriptionMoved(evt); + } + } + + + /** + * Notifies all registered listeners of the new event. + * + * @param source the contact that has caused the event. + * @param eventID an identifier of the event to dispatch. + */ + public void fireServerStoredGroupEvent(ContactGroupRssImpl source, + int eventID) + { + ServerStoredGroupEvent evt = new ServerStoredGroupEvent( + source, eventID, (ContactGroupRssImpl)source.getParentContactGroup() + , this.parentProvider, this); + + Iterator listeners = null; + synchronized (serverStoredGroupListeners) + { + listeners = new ArrayList(serverStoredGroupListeners).iterator(); + } + + while (listeners.hasNext()) + { + ServerStoredGroupListener listener + = (ServerStoredGroupListener) listeners.next(); + + if(eventID == ServerStoredGroupEvent.GROUP_CREATED_EVENT) + { + listener.groupCreated(evt); + } + else if(eventID == ServerStoredGroupEvent.GROUP_RENAMED_EVENT) + { + listener.groupNameChanged(evt); + } + else if(eventID == ServerStoredGroupEvent.GROUP_REMOVED_EVENT) + { + listener.groupRemoved(evt); + } + } + } + + /** + * Notifies all registered listeners of the new event. + * + * @param oldValue the presence status we were in before the change. + */ + public void fireProviderStatusChangeEvent(PresenceStatus oldValue) + { + ProviderPresenceStatusChangeEvent evt + = new ProviderPresenceStatusChangeEvent(this.parentProvider, + oldValue, this.getPresenceStatus()); + + Iterator listeners = null; + synchronized (providerPresenceStatusListeners) + { + listeners = new ArrayList(providerPresenceStatusListeners).iterator(); + } + + while (listeners.hasNext()) + { + ProviderPresenceStatusListener listener + = (ProviderPresenceStatusListener) listeners.next(); + + //listener.providerStatusChanged(evt); + } + } + + /** + * Rss implementation of the corresponding ProtocolProviderService + * method. + * + * @param listener a dummy param. + */ + public void addProviderPresenceStatusListener( + ProviderPresenceStatusListener listener) + { + synchronized(providerPresenceStatusListeners) + { + if (!providerPresenceStatusListeners.contains(listener)) + this.providerPresenceStatusListeners.add(listener); + } + } + + /** + * Registers a listener that would receive events upon changes in server + * stored groups. + * + * @param listener a ServerStoredGroupChangeListener impl that would + * receive events upong group changes. + */ + public void addServerStoredGroupChangeListener(ServerStoredGroupListener + listener) + { + synchronized(serverStoredGroupListeners) + { + if (!serverStoredGroupListeners.contains(listener)) + serverStoredGroupListeners.add(listener); + } + } + + /** + * Rss implementation of the corresponding ProtocolProviderService + * method. + * + * @param listener the SubscriptionListener to register + */ + public void addSubsciptionListener(SubscriptionListener listener) + { + synchronized(subscriptionListeners) + { + if (!subscriptionListeners.contains(listener)) + this.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. + */ + public void createServerStoredContactGroup(ContactGroup parent, + String groupName) + { + ContactGroupRssImpl newGroup + = new ContactGroupRssImpl(groupName, parentProvider); + + ((ContactGroupRssImpl)parent).addSubgroup(newGroup); + + this.fireServerStoredGroupEvent( + newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); + } + + /** + * A Rss Provider method to use for fast filling of a contact list. + * + * @param contactGroup the group to add + */ + public void addRssGroup(ContactGroupRssImpl contactGroup) + { + contactListRoot.addSubgroup(contactGroup); + } + + /** + * A Rss Provider method to use for fast filling of a contact list. + * This method would add both the group and fire an event. + * + * @param parent the group where <tt>contactGroup</tt> should be added. + * @param contactGroup the group to add + */ + public void addRssGroupAndFireEvent( + ContactGroupRssImpl parent + , ContactGroupRssImpl contactGroup) + { + parent.addSubgroup(contactGroup); + + this.fireServerStoredGroupEvent( + contactGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); + } + + + /** + * 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 contactListRoot.findContactByID(contactID); + } + + /** + * Sets the specified status message. + * @param statusMessage a String containing the new status message. + */ + public void setStatusMessage(String statusMessage) + { + this.statusMessage = statusMessage; + } + + /** + * Returns the status message that was last set through + * setCurrentStatusMessage. + * + * @return the last status message that we have requested and the aim + * server has confirmed. + */ + public String getCurrentStatusMessage() + { + return statusMessage; + } + + /** + * 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 presenceStatus; + } + + /** + * 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 contactListRoot; + } + + /** + * 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 RssStatusEnum.supportedStatusSet(); + } + + public ContactGroupRssImpl getContactListRoot(){ + return this.contactListRoot; + } + + /** + * 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) + { + ContactRssImpl rssContact + = (ContactRssImpl)contactToMove; + + ContactGroupRssImpl parentRssGroup + = findContactParent(rssContact); + + parentRssGroup.removeContact(rssContact); + + //if this is a volatile contact then we haven't really subscribed to + //them so we'd need to do so here + if(!rssContact.isPersistent()) + { + //first tell everyone that the volatile contact was removed + fireSubscriptionEvent(rssContact + , parentRssGroup + , SubscriptionEvent.SUBSCRIPTION_REMOVED); + + try + { + //now subscribe + this.subscribe(newParent, contactToMove.getAddress()); + + //now tell everyone that we've added the contact + fireSubscriptionEvent(rssContact + , newParent + , SubscriptionEvent.SUBSCRIPTION_CREATED); + } + catch (Exception ex) + { + logger.error("Failed to move contact " + + rssContact.getAddress() + , ex); + } + } + else + { + ( (ContactGroupRssImpl) newParent) + .addContact(rssContact); + + fireSubscriptionMovedEvent(contactToMove + , parentRssGroup + , 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 + { + PresenceStatus oldPresenceStatus = this.presenceStatus; + this.presenceStatus = status; + this.statusMessage = statusMessage; + + this.fireProviderStatusChangeEvent(oldPresenceStatus); + + //since we are not a real protocol, we set the contact presence status + //ourselves and make them have the same status as ours. + changePresenceStatusForAllContacts( getServerStoredContactListRoot() + , getPresenceStatus()); + + //now check whether we are in someone else's contact list and modify + //our status there + List contacts = findContactsPointingToUs(); + + Iterator contactsIter = contacts.iterator(); + while (contactsIter.hasNext()) + { + ContactRssImpl contact + = (ContactRssImpl) contactsIter.next(); + + PresenceStatus oldStatus = contact.getPresenceStatus(); + contact.setPresenceStatus(status); + contact.getParentPresenceOperationSet() + .fireContactPresenceStatusChangeEvent( + contact + , contact.getParentContactGroup() + , oldStatus); + + } + } + + + + /** + * 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 + { + return findContactByID(contactIdentifier).getPresenceStatus(); + } + + /** + * Sets the presence status of <tt>contact</tt> to <tt>newStatus</tt>. + * + * @param contact the <tt>ContactRssImpl</tt> whose status we'd like + * to set. + * @param newStatus the new status we'd like to set to <tt>contact</tt>. + */ + private void changePresenceStatusForContact( + ContactRssImpl contact + , PresenceStatus newStatus) + { + PresenceStatus oldStatus = contact.getPresenceStatus(); + contact.setPresenceStatus(newStatus); + + fireContactPresenceStatusChangeEvent( + contact, findContactParent(contact), oldStatus); + } + + /** + * Sets the presence status of all <tt>contact</tt>s in our contact list + * (except those that correspond to another provider registered with SC) + * to <tt>newStatus</tt>. + * + * @param newStatus the new status we'd like to set to <tt>contact</tt>. + * @param parent the group in which we'd have to update the status of all + * direct and indirect child contacts. + */ + private void changePresenceStatusForAllContacts(ContactGroup parent, + PresenceStatus newStatus) + { + //first set the status for contacts in this group + Iterator childContacts = parent.contacts(); + + while(childContacts.hasNext()) + { + ContactRssImpl contact + = (ContactRssImpl)childContacts.next(); + + if(findProviderForRssUserID(contact.getAddress()) != null) + { + //this is a contact corresponding to another SIP Communicator + //provider so we won't change it's status here. + continue; + } + PresenceStatus oldStatus = contact.getPresenceStatus(); + contact.setPresenceStatus(newStatus); + + fireContactPresenceStatusChangeEvent( + contact, parent, oldStatus); + } + + //now call this method recursively for all subgroups + Iterator subgroups = parent.subgroups(); + + while(subgroups.hasNext()) + { + ContactGroup subgroup = (ContactGroup)subgroups.next(); + changePresenceStatusForAllContacts(subgroup, newStatus); + } + } + + + /** + * 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) + { + this.providerPresenceStatusListeners.remove(listener); + } + } + + /** + * Returns the group that is parent of the specified rssGroup or null + * if no parent was found. + * @param rssGroup the group whose parent we're looking for. + * @return the ContactGroupRssImpl instance that rssGroup + * belongs to or null if no parent was found. + */ + public ContactGroupRssImpl findGroupParent(ContactGroupRssImpl rssGroup) + { + return contactListRoot.findGroupParent(rssGroup); + } + + /** + * Returns the group that is parent of the specified rssContact or + * null if no parent was found. + * @param rssContact the contact whose parent we're looking for. + * @return the ContactGroupRssImpl instance that rssContact + * belongs to or null if no parent was found. + */ + public ContactGroupRssImpl findContactParent( + ContactRssImpl rssContact) + { + return (ContactGroupRssImpl)rssContact.getParentContactGroup(); + } + + /** + * Removes the specified group from the server stored contact list. + * + * @param group the group to remove. + * + * @throws IllegalArgumentException if <tt>group</tt> was not found in this + * protocol's contact list. + */ + public void removeServerStoredContactGroup(ContactGroup group) + throws IllegalArgumentException + { + ContactGroupRssImpl rssGroup = (ContactGroupRssImpl)group; + + ContactGroupRssImpl parent = findGroupParent(rssGroup); + + if(parent == null){ + throw new IllegalArgumentException( + "group " + group + + " does not seem to belong to this protocol's contact list."); + } + + parent.removeSubGroup(rssGroup); + + this.fireServerStoredGroupEvent( + rssGroup, ServerStoredGroupEvent.GROUP_REMOVED_EVENT); + } + + + /** + * 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) + { + synchronized(serverStoredGroupListeners) + { + serverStoredGroupListeners.remove(listener); + } + } + + /** + * Removes the specified subscription listener. + * + * @param listener the listener to remove. + */ + public void removeSubscriptionListener(SubscriptionListener listener) + { + synchronized(subscriptionListeners) + { + this.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) + { + ((ContactGroupRssImpl)group).setGroupName(newName); + + this.fireServerStoredGroupEvent( + (ContactGroupRssImpl)group, + ServerStoredGroupEvent.GROUP_RENAMED_EVENT); + } + + /** + * 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.authorizationHandler = 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 + { + ContactRssImpl contact = new ContactRssImpl( + contactIdentifier + , parentProvider); + + ((ContactGroupRssImpl)parent).addContact(contact); + + + fireSubscriptionEvent(contact, + parent, + SubscriptionEvent.SUBSCRIPTION_CREATED); + + //if the newly added contact corresponds to another provider - set their + //status accordingly + ProtocolProviderServiceRssImpl rssProvider + = findProviderForRssUserID(contactIdentifier); + if(rssProvider != null) + { + OperationSetPersistentPresence opSetPresence + = (OperationSetPersistentPresence)rssProvider.getOperationSet( + OperationSetPersistentPresence.class); + + changePresenceStatusForContact( + contact + , (RssStatusEnum)opSetPresence.getPresenceStatus()); + } + else + { + //otherwise - since we are not a real protocol, we set the contact + //presence status ourselves + changePresenceStatusForContact(contact, getPresenceStatus()); + } + //just after inscription of the new contact, looking for the feed + this.parentProvider.getBasicInstantMessaging().newContact(contact); + } + + /** + * Depending on whether <tt>contact</tt> corresponds to another protocol + * provider installed in sip-communicator, this method would either deliver + * it to that provider or simulate a corresponding request from the + * destination contact and make return a response after it has received + * one If the destination contact matches us, then we'll ask the user to + * act upon the request, and return the response. + * + * @param request the authorization request that we'd like to deliver to the + * desination <tt>contact</tt>. + * @param contact the <tt>Contact</tt> to notify + * + * @return the <tt>AuthorizationResponse</tt> that has been given or + * generated in response to <tt>request</tt>. + */ + private AuthorizationResponse deliverAuthorizationRequest( + AuthorizationRequest request, + Contact contact) + { + String userID = contact.getAddress(); + + //if the user id is our own id, then this request is being routed to us + //from another instance of the rss provider. + if (userID.equals(this.parentProvider.getAccountID().getUserID())) + { + //check who is the provider sending the message + String sourceUserID = contact.getProtocolProvider() + .getAccountID().getUserID(); + + //check whether they are in our contact list + Contact from = findContactByID(sourceUserID); + + //and if not - add them there as volatile. + if (from == null) + { + from = createVolatileContact(sourceUserID); + } + + //and now handle the request. + return authorizationHandler.processAuthorisationRequest( + request, from); + } + else + { + //if userID is not our own, try a check whether another provider + //has that id and if yes - deliver the request to them. + ProtocolProviderServiceRssImpl rssProvider + = this.findProviderForRssUserID(userID); + if (rssProvider != null) + { + OperationSetPersistentPresenceRssImpl opSetPersPresence + = (OperationSetPersistentPresenceRssImpl) + rssProvider.getOperationSet( + OperationSetPersistentPresence.class); + return opSetPersPresence + .deliverAuthorizationRequest(request, contact); + } + else + { + //if we got here then "to" is simply someone in our contact + //list so let's just simulate a reciproce request and generate + //a response accordingly. + + //pretend that the remote contact is asking for authorization + authorizationHandler.processAuthorisationRequest( + request, contact); + + //and now pretend that the remote contact has granted us + //authorization + return new AuthorizationResponse(AuthorizationResponse.ACCEPT + , "You are welcome!"); + } + } + } + + /** + * 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 + { + subscribe(contactListRoot, 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 + { + ContactGroupRssImpl parentGroup + = (ContactGroupRssImpl)((ContactRssImpl)contact) + .getParentContactGroup(); + + parentGroup.removeContact((ContactRssImpl)contact); + + fireSubscriptionEvent((ContactRssImpl)contact, + ((ContactRssImpl)contact).getParentContactGroup(), + SubscriptionEvent.SUBSCRIPTION_REMOVED); + } + + /** + * Creates and returns a unresolved contact from the specified + * <tt>address</tt> and <tt>persistentData</tt>. The method will not try + * to establish a network connection and resolve the newly created Contact + * against the server. The protocol provider may will later try and resolve + * the contact. When this happens the corresponding event would notify + * interested subscription listeners. + * + * @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 from the specified + * <tt>address</tt> and <tt>persistentData</tt>. The method will not try + * to establish a network connection and resolve the newly created Contact + * against the server. The protocol provider may will later try and resolve + * the contact. When this happens the corresponding event would notify + * interested subscription listeners. + * + * @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 parent 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 parent) + { + ContactRssImpl contact = new ContactRssImpl( + address + , parentProvider); + contact.setResolved(false); + + ( (ContactGroupRssImpl) parent).addContact(contact); + + fireSubscriptionEvent(contact, + parent, + SubscriptionEvent.SUBSCRIPTION_CREATED); + + //since we don't have any server, we'll simply resolve the contact + //ourselves as if we've just received an event from the server telling + //us that it has been resolved. + fireSubscriptionEvent( + contact, parent, SubscriptionEvent.SUBSCRIPTION_RESOLVED); + + //since we are not a real protocol, we set the contact presence status + //ourselves + changePresenceStatusForContact( contact, getPresenceStatus()); + + //we retrieve if exists the persistent data for this contact + //which represents the date of the last item seen by the user + contact.setPersistentData(persistentData); + + return contact; + } + + /** + * Looks for a rss protocol provider registered for a user id matching + * <tt>rssUserID</tt>. + * + * @param rssUserID the ID of the Rss user whose corresponding + * protocol provider we'd like to find. + * @return ProtocolProviderServiceRssImpl a rss protocol + * provider registered for a user with id <tt>rssUserID</tt> or null + * if there is no such protocol provider. + */ + public ProtocolProviderServiceRssImpl + findProviderForRssUserID(String rssUserID) + { + BundleContext bc = RssActivator.getBundleContext(); + + String osgiQuery = "(&"+ + "(" + ProtocolProviderFactory.PROTOCOL + "=Rss)" + + "(" + ProtocolProviderFactory.USER_ID + + "=" + rssUserID + "))"; + + ServiceReference[] refs = null; + try + { + refs = bc.getServiceReferences( + ProtocolProviderService.class.getName(), + osgiQuery); + } + catch (InvalidSyntaxException ex) + { + logger.error("Failed to execute the following osgi query: " + + osgiQuery + , ex); + } + + if(refs != null && refs.length > 0) + { + return (ProtocolProviderServiceRssImpl)bc.getService(refs[0]); + } + + return null; + } + + /** + * Looks for rss protocol providers that have added us to their + * contact list and returns list of all contacts representing us in these + * providers. + * + * @return a list of all contacts in other providers' contact lists that + * point to us. + */ + public List findContactsPointingToUs() + { + List contacts = new LinkedList(); + BundleContext bc = RssActivator.getBundleContext(); + + String osgiQuery = + "(" + ProtocolProviderFactory.PROTOCOL + + "=Rss)"; + + ServiceReference[] refs = null; + try + { + refs = bc.getServiceReferences( + ProtocolProviderService.class.getName(), osgiQuery); + } + catch (InvalidSyntaxException ex) + { + logger.error("Failed to execute the following osgi query: " + + osgiQuery, ex); + } + + for (int i =0; refs != null && i < refs.length; i++) + { + ProtocolProviderServiceRssImpl gibProvider + = (ProtocolProviderServiceRssImpl)bc.getService(refs[i]); + + OperationSetPersistentPresenceRssImpl opSetPersPresence + = (OperationSetPersistentPresenceRssImpl)gibProvider + .getOperationSet(OperationSetPersistentPresence.class); + + Contact contact = opSetPersPresence.findContactByID( + parentProvider.getAccountID().getUserID()); + + if (contact != null) + contacts.add(contact); + } + + return contacts; + } + + + /** + * Creates and returns a unresolved contact group from the specified + * <tt>address</tt> and <tt>persistentData</tt>. The method will not try + * to establish a network connection and resolve the newly created + * <tt>ContactGroup</tt> against the server or the contact itself. The + * protocol provider will later resolve the contact group. When this happens + * the corresponding event would notify interested subscription listeners. + * + * @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) + { + ContactGroupRssImpl newGroup + = new ContactGroupRssImpl( + ContactGroupRssImpl.createNameFromUID(groupUID) + , parentProvider); + newGroup.setResolved(false); + + //if parent is null then we're adding under root. + if(parentGroup == null) + parentGroup = getServerStoredContactListRoot(); + + ((ContactGroupRssImpl)parentGroup).addSubgroup(newGroup); + + this.fireServerStoredGroupEvent( + newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); + + return newGroup; + } + + private class UnregistrationListener + implements RegistrationStateChangeListener + { + /** + * The method is called by a ProtocolProvider implementation whenver + * a change in the registration state of the corresponding provider had + * occurred. The method is particularly interested in events stating + * that the rss provider has unregistered so that it would fire + * status change events for all contacts in our buddy list. + * + * @param evt ProviderStatusChangeEvent the event describing the status + * change. + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + if (! evt.getNewState().equals(RegistrationState.UNREGISTERED) + && !evt.getNewState().equals(RegistrationState.AUTHENTICATION_FAILED) + && !evt.getNewState().equals(RegistrationState.CONNECTION_FAILED)) + { + return; + } + + //send event notifications saying that all our buddies are + //offline. The icq 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()) + { + ContactGroupRssImpl group + = (ContactGroupRssImpl) groupsIter.next(); + + Iterator contactsIter = group.contacts(); + + while (contactsIter.hasNext()) + { + ContactRssImpl contact + = (ContactRssImpl) contactsIter.next(); + + PresenceStatus oldContactStatus + = contact.getPresenceStatus(); + + if (!oldContactStatus.isOnline()) + continue; + + contact.setPresenceStatus(RssStatusEnum.ONLINE); + + fireContactPresenceStatusChangeEvent( + contact + , contact.getParentContactGroup() + , oldContactStatus); + } + } + } + } + + /** + * Returns the volatile group or null if this group has not yet been + * created. + * + * @return a volatile group existing in our contact list or <tt>null</tt> + * if such a group has not yet been created. + */ + private ContactGroupRssImpl getNonPersistentGroup() + { + for (int i = 0 + ; i < getServerStoredContactListRoot().countSubgroups() + ; i++) + { + ContactGroupRssImpl gr = + (ContactGroupRssImpl)getServerStoredContactListRoot() + .getGroup(i); + + if(!gr.isPersistent()) + return gr; + } + + return null; + } + + + /** + * 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 contactAddress the address of the volatile contact we'd like to + * create. + * @return the newly created volatile contact. + */ + public ContactRssImpl createVolatileContact(String contactAddress) + { + //First create the new volatile contact; + ContactRssImpl newVolatileContact + = new ContactRssImpl(contactAddress + , this.parentProvider); + newVolatileContact.setPersistent(false); + + + //Check whether a volatile group already exists and if not create + //one + ContactGroupRssImpl theVolatileGroup = getNonPersistentGroup(); + + + //if the parent volatile group is null then we create it + if (theVolatileGroup == null) + { + List emptyBuddies = new LinkedList(); + theVolatileGroup = new ContactGroupRssImpl( + "NotInContactList" + , parentProvider); + theVolatileGroup.setResolved(false); + theVolatileGroup.setPersistent(false); + theVolatileGroup.addContact(newVolatileContact); + + this.contactListRoot.addSubgroup(theVolatileGroup); + + fireServerStoredGroupEvent(theVolatileGroup, + ServerStoredGroupEvent.GROUP_CREATED_EVENT); + } + + //now add the volatile contact instide it + theVolatileGroup.addContact(newVolatileContact); + fireSubscriptionEvent(newVolatileContact, + theVolatileGroup, + SubscriptionEvent.SUBSCRIPTION_CREATED); + + return newVolatileContact; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/OperationSetTypingNotificationsRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/OperationSetTypingNotificationsRssImpl.java new file mode 100644 index 0000000..08f71ea --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/OperationSetTypingNotificationsRssImpl.java @@ -0,0 +1,193 @@ +/* + * 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.rss; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Implements typing notifications for the Rss protocol. The operation + * set would simply mirror all outgoing typing notifications and make them + * appear as incoming events generated by the contact that we are currently + * writing a message to. + * + * @author Emil Ivov + */ +public class OperationSetTypingNotificationsRssImpl + implements OperationSetTypingNotifications +{ + private static final Logger logger = + Logger.getLogger(OperationSetTypingNotificationsRssImpl.class); + + /** + * All currently registered TN listeners. + */ + private List typingNotificationsListeners = new ArrayList(); + + /** + * The provider that created us. + */ + private ProtocolProviderServiceRssImpl parentProvider = null; + + /** + * The currently valid persistent presence operation set.. + */ + private OperationSetPersistentPresenceRssImpl opSetPersPresence = null; + + + /** + * Creates a new instance of this operation set and keeps the parent + * provider as a reference. + * + * @param provider a ref to the <tt>ProtocolProviderServiceImpl</tt> + * that created us and that we'll use for retrieving the underlying aim + * connection. + * @param opSetPersPresence the currently valid + * <tt>OperationSetPersistentPresenceRssImpl</tt> instance. + */ + OperationSetTypingNotificationsRssImpl( + ProtocolProviderServiceRssImpl provider, + OperationSetPersistentPresenceRssImpl opSetPersPresence) + { + this.parentProvider = provider; + this.opSetPersPresence = opSetPersPresence; + } + + /** + * Adds <tt>listener</tt> to the list of listeners registered for receiving + * <tt>TypingNotificationEvent</tt>s + * + * @param listener the <tt>TypingNotificationsListener</tt> listener that + * we'd like to add to the list of listeneres registered for receiving + * typing notificaions. + */ + public void addTypingNotificationsListener( + TypingNotificationsListener listener) + { + synchronized(typingNotificationsListeners) + { + typingNotificationsListeners.add(listener); + } + } + + /** + * Removes <tt>listener</tt> from the list of listeners registered for + * receiving <tt>TypingNotificationEvent</tt>s + * + * @param listener the <tt>TypingNotificationsListener</tt> listener that + * we'd like to remove + */ + public void removeTypingNotificationsListener( + TypingNotificationsListener listener) + { + synchronized(typingNotificationsListeners) + { + typingNotificationsListeners.remove(listener); + } + } + + /** + * 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 + { + if( !(notifiedContact instanceof ContactRssImpl) ) + throw new IllegalArgumentException( + "The specified contact is not a Rss contact." + + notifiedContact); + + + String userID = notifiedContact.getAddress(); + + //if the user id is owr own id, then this message is being routed to us + //from another instance of the rss provider. + if (userID.equals(this.parentProvider.getAccountID().getUserID())) + { + //check who is the provider sending the message + String sourceUserID = notifiedContact.getProtocolProvider() + .getAccountID().getUserID(); + + //check whether they are in our contact list + Contact from = opSetPersPresence.findContactByID(sourceUserID); + + //and if not - add them there as volatile. + if (from == null) + { + from = opSetPersPresence.createVolatileContact(sourceUserID); + } + + //and now fire the message received event. + fireTypingNotificationsEvent(from, typingState); + } + else + { + //if userID is not our own, try a check whether another provider + //has that id and if yes - deliver the message to them. + ProtocolProviderServiceRssImpl rssProvider + = this.opSetPersPresence.findProviderForRssUserID(userID); + if (rssProvider != null) + { + OperationSetTypingNotificationsRssImpl opSetTN + = (OperationSetTypingNotificationsRssImpl) + rssProvider.getOperationSet( + OperationSetTypingNotifications.class); + opSetTN.sendTypingNotification(notifiedContact, typingState); + } + else + { + //if we got here then "to" is simply someone in our contact + //list so let's just echo the message. + fireTypingNotificationsEvent(notifiedContact, typingState); + } + } + } + +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/ProtocolIconRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/ProtocolIconRssImpl.java new file mode 100644 index 0000000..b91b5df --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/ProtocolIconRssImpl.java @@ -0,0 +1,102 @@ +/* + * 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.rss; + +import java.awt.image.*; +import java.io.*; +import java.net.*; +import java.util.*; + +import javax.imageio.*; +import javax.imageio.stream.*; + +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.impl.gui.utils.ImageLoader.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * Reperesents the Rss protocol icon. Implements the <tt>ProtocolIcon</tt> + * interface in order to provide a rss logo image in two different sizes. + * + * @author Yana Stamcheva + */ +public class ProtocolIconRssImpl + implements ProtocolIcon +{ + private static Logger logger + = Logger.getLogger(ProtocolIconRssImpl.class); + + /** + * A hash table containing the protocol icon in different sizes. + */ + private static Hashtable iconsTable = new Hashtable(); + static + { + iconsTable.put(ProtocolIcon.ICON_SIZE_16x16, + loadIcon("resources/images/rss/rss-online.png")); + + iconsTable.put(ProtocolIcon.ICON_SIZE_64x64, + loadIcon("resources/images/rss/rss64x64.png")); + } + + /** + * Implements the <tt>ProtocolIcon.getSupportedSizes()</tt> method. Returns + * an iterator to a set containing the supported icon sizes. + * @return an iterator to a set containing the supported icon sizes + */ + public Iterator getSupportedSizes() + { + return iconsTable.keySet().iterator(); + } + + /** + * Returne TRUE if a icon with the given size is supported, FALSE-otherwise. + */ + public boolean isSizeSupported(String iconSize) + { + return iconsTable.containsKey(iconSize); + } + + /** + * Returns the icon image in the given size. + * @param iconSize the icon size; one of ICON_SIZE_XXX constants + */ + public byte[] getIcon(String iconSize) + { + return (byte[])iconsTable.get(iconSize); + } + + /** + * Returns the icon image used to represent the protocol connecting state. + * @return the icon image used to represent the protocol connecting state + */ + public byte[] getConnectingIcon() + { + return loadIcon("resources/images/rss/rss-online.png"); + } + + /** + * Loads an image from a given image path. + * @param imagePath The identifier of the image. + * @return The image for the given identifier. + */ + public static byte[] loadIcon(String imagePath) + { + InputStream is = ProtocolIconRssImpl.class + .getClassLoader().getResourceAsStream(imagePath); + + byte[] icon = null; + try { + icon = new byte[is.available()]; + is.read(icon); + } catch (IOException e) { + logger.error("Failed to load icon: " + imagePath, e); + } + return icon; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/ProtocolProviderFactoryRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/ProtocolProviderFactoryRssImpl.java new file mode 100644 index 0000000..de59fa7 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/ProtocolProviderFactoryRssImpl.java @@ -0,0 +1,272 @@ +/* + * 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.rss; + +import java.util.*; + +import org.osgi.framework.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * The Rss protocol provider factory creates instances of the Rss + * protocol provider service. One Service instance corresponds to one account. + * + * @author Emil Ivov + */ +public class ProtocolProviderFactoryRssImpl + extends ProtocolProviderFactory +{ + private static final Logger logger + = Logger.getLogger(ProtocolProviderFactoryRssImpl.class); + + /** + * The table that we store our accounts in. + */ + private Hashtable registeredAccounts = new Hashtable(); + + + /** + * Creates an instance of the ProtocolProviderFactoryRssImpl. + */ + public ProtocolProviderFactoryRssImpl() + { + super(); + } + + /** + * 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(); + } + + /** + * Returns a copy of the list containing the <tt>AccoudID</tt>s of all + * accounts currently registered in this protocol provider. + * + * @return a copy of the list containing the <tt>AccoudID</tt>s of all + * accounts currently registered in this protocol provider. + */ + public ArrayList getRegisteredAccounts() + { + return new ArrayList(registeredAccounts.keySet()); + } + + /** + * Loads (and hence installs) all accounts previously stored in the + * configuration service. + */ + public void loadStoredAccounts() + { + super.loadStoredAccounts( RssActivator.getBundleContext()); + } + + + /** + * Initializaed and creates an account corresponding to the specified + * accountProperties and registers the resulting ProtocolProvider in the + * <tt>context</tt> BundleContext parameter. + * + * @param userIDStr tha/a user identifier uniquely representing the newly + * created account within the protocol namespace. + * @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 + = RssActivator.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 RssAccountID(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 (through the UI) a call to the + //ProtocolProviderService.register() method and it needs to acces + //the configuration service and check for a stored password. + this.storeAccount( + RssActivator.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 loaded account + */ + public AccountID loadAccount( Map accountProperties) + { + BundleContext context + = RssActivator.getBundleContext(); + if(context == null) + throw new NullPointerException("The specified BundleContext was null"); + + String userIDStr = (String)accountProperties.get(USER_ID); + + AccountID accountID = new RssAccountID(userIDStr, accountProperties); + + //get a reference to the configuration service and register whatever + //properties we have in it. + + Hashtable properties = new Hashtable(); + properties.put(PROTOCOL, "Rss"); + properties.put(USER_ID, userIDStr); + + ProtocolProviderServiceRssImpl rssProtocolProvider + = new ProtocolProviderServiceRssImpl(); + + rssProtocolProvider.initialize(userIDStr, accountID); + + ServiceRegistration registration + = context.registerService( ProtocolProviderService.class.getName(), + rssProtocolProvider, + properties); + + registeredAccounts.put(accountID, registration); + return accountID; + } + + + /** + * Removes the specified account from the list of accounts that this + * provider factory is handling. + * + * @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) + { + //unregister the protocol provider + ServiceReference serRef = getProviderForAccount(accountID); + + ProtocolProviderService protocolProvider + = (ProtocolProviderService) RssActivator.getBundleContext() + .getService(serRef); + + try + { + protocolProvider.unregister(); + } + catch (OperationFailedException exc) + { + logger.error("Failed to unregister protocol provider for account : " + + accountID + " caused by : " + exc); + } + + ServiceRegistration registration + = (ServiceRegistration)registeredAccounts.remove(accountID); + + if(registration == null) + return false; + + //kill the service + registration.unregister(); + + registeredAccounts.remove(accountID); + + return removeStoredAccount(RssActivator.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(RssActivator.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(RssActivator.getBundleContext(), accountID ); + } + + /** + * 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()); + } + } + +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/ProtocolProviderServiceRssImpl.java b/src/net/java/sip/communicator/impl/protocol/rss/ProtocolProviderServiceRssImpl.java new file mode 100644 index 0000000..669a956 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/ProtocolProviderServiceRssImpl.java @@ -0,0 +1,447 @@ +/* + * 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.rss; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * A Rss implementation of the ProtocolProviderService. + * + * @author Emil Ivov/Jean-Albert Vescovo + */ +public class ProtocolProviderServiceRssImpl + implements ProtocolProviderService +{ + private static final Logger logger + = Logger.getLogger(ProtocolProviderServiceRssImpl.class); + + /** + * The name of this protocol. + */ + public static final String RSS_PROTOCOL_NAME = "Rss"; + + /** + * The id of the account that this protocol provider represents. + */ + private AccountID accountID = null; + + /** + * We use this to lock access to initialization. + */ + private Object initializationLock = new Object(); + + /** + * The hashtable with the operation sets that we support locally. + */ + private Hashtable supportedOperationSets = new Hashtable(); + + /** + * A list of listeners interested in changes in our registration state. + */ + private Vector registrationStateListeners = new Vector(); + + /** + * Indicates whether or not the provider is initialized and ready for use. + */ + private boolean isInitialized = false; + + /** + * The logo corresponding to the rss protocol. + */ + private ProtocolIconRssImpl rssIcon + = new ProtocolIconRssImpl(); + + /** + * A reference to the IM operation set + */ + private OperationSetBasicInstantMessagingRssImpl basicInstantMessaging; + + private boolean start = false; + + /** + * The registration state that we are currently in. Note that in a real + * world protocol implementation this field won't exist and the registration + * state would be retrieved from the protocol stack. + */ + private RegistrationState currentRegistrationState + = RegistrationState.UNREGISTERED; + + /** + * The default constructor for the Rss protocol provider. + */ + public ProtocolProviderServiceRssImpl() + { + logger.trace("Creating a rss provider."); + } + + /** + * Initializes the service implementation, and puts it in a state 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 userID the user id of the rss account we're currently + * initializing + * @param accountID the identifier of the account that this protocol + * provider represents. + * + * @see net.java.sip.communicator.service.protocol.AccountID + */ + protected void initialize(String userID, + AccountID accountID) + { + synchronized(initializationLock) + { + this.accountID = accountID; + + //initialize the presence operationset + OperationSetPersistentPresenceRssImpl persistentPresence = + new OperationSetPersistentPresenceRssImpl(this); + + supportedOperationSets.put( + OperationSetPersistentPresence.class.getName(), + persistentPresence); + + + //register it once again for those that simply need presence and + //won't be smart enough to check for a persistent presence + //alternative + supportedOperationSets.put( OperationSetPresence.class.getName(), + persistentPresence); + + //initialize the IM operation set + //OperationSetBasicInstantMessagingRssImpl + basicInstantMessaging + = new OperationSetBasicInstantMessagingRssImpl( + this + , (OperationSetPersistentPresenceRssImpl) + persistentPresence); + + supportedOperationSets.put( + OperationSetBasicInstantMessaging.class.getName(), + basicInstantMessaging); + + //initialize the typing notifications operation set + OperationSetTypingNotifications typingNotifications = + new OperationSetTypingNotificationsRssImpl( + this, persistentPresence); + + supportedOperationSets.put( + OperationSetTypingNotifications.class.getName(), + typingNotifications); + + isInitialized = true; + } + } + + /** + * 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(registrationStateListeners) + { + if (!registrationStateListeners.contains(listener)) + registrationStateListeners.add(listener); + } + + } + + /** + * Removes the specified registration listener so that it won't receive + * further notifications when our registration state changes. + * + * @param listener the listener to remove. + */ + public void removeRegistrationStateChangeListener( + RegistrationStateChangeListener listener) + { + synchronized(registrationStateListeners) + { + registrationStateListeners.remove(listener); + } + } + + /** + * Creates a <tt>RegistrationStateChangeEvent</tt> 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. + */ + private void fireRegistrationStateChanged( RegistrationState oldState, + RegistrationState newState, + int reasonCode, + String reason) + { + RegistrationStateChangeEvent event = + new RegistrationStateChangeEvent( + this, oldState, newState, reasonCode, reason); + + logger.debug("Dispatching " + event + " to " + + registrationStateListeners.size()+ " listeners."); + + Iterator listeners = null; + synchronized (registrationStateListeners) + { + listeners = new ArrayList(registrationStateListeners).iterator(); + } + + while (listeners.hasNext()) + { + RegistrationStateChangeListener listener + = (RegistrationStateChangeListener) listeners.next(); + + listener.registrationStateChanged(event); + } + + /* If Timer isn't started, we launch a new timer for sending periodic + * rss feeds' refresh taks. + * If yes, we stop it. + */ + if(!start){ + this.basicInstantMessaging.createTimer(); + start = true; + } + else{ + this.basicInstantMessaging.stopTimer(); + start = false; + } + + logger.trace("Done."); + } + + + /** + * 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 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()); + } + + /** + * Returns the short name of the protocol that the implementation of this + * provider is based upon (like SIP, Jabber, ICQ/AIM, or others for + * example). + * + * @return a String containing the short name of the protocol this + * service is implementing (most often that would be a name in + * ProtocolNames). + */ + public String getProtocolName() + { + return RSS_PROTOCOL_NAME; + } + + /** + * Returns the state of the registration of this protocol provider with + * the corresponding registration service. + * + * @return ProviderRegistrationState + */ + public RegistrationState getRegistrationState() + { + return currentRegistrationState; + } + + /** + * Returns an array containing all operation sets supported by the + * current implementation. + * + * @return a java.util.Map containing instance of all supported + * operation sets mapped against their class names (e.g. + * OperationSetPresence.class.getName()) . + */ + public Map getSupportedOperationSets() + { + //Copy the map so that the caller is not able to modify it. + return (Map)supportedOperationSets.clone(); + } + + /** + * Indicates whether or not this provider is registered + * + * @return true if the provider is currently registered and false + * otherwise. + */ + public boolean isRegistered() + { + return currentRegistrationState.equals(RegistrationState.REGISTERED); + } + + /** + * Starts the registration process. + * + * @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(SecurityAuthority authority) + throws OperationFailedException + { + //we don't really need a password here since there's no server in + //Rss but nevertheless we'll behave as if we did. + + //verify whether a password has already been stored for this account + String password = RssActivator. + getProtocolProviderFactory().loadPassword(getAccountID()); + + //if we don't - retrieve it from the user through the security authority + 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("Rss" + , 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 the user indicated that the password should be saved, we'll ask + //the proto provider factory to store it for us. + if (credentials.isPasswordPersistent()) + { + RssActivator.getProtocolProviderFactory() + .storePassword(getAccountID(), password); + } + } + + + RegistrationState oldState = currentRegistrationState; + currentRegistrationState = RegistrationState.REGISTERED; + + fireRegistrationStateChanged( + oldState + , currentRegistrationState + , RegistrationStateChangeEvent.REASON_USER_REQUEST + , null); + } + + /** + * 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() + { + if(!isInitialized) + { + return; + } + logger.trace("Killing the Rss Protocol Provider."); + + if(isRegistered()) + { + try + { + //do the unregistration + unregister(); + } + catch (OperationFailedException ex) + { + //we're shutting down so we need to silence the exception here + logger.error( + "Failed to properly unregister before shutting down. " + + getAccountID() + , ex); + } + } + + isInitialized = false; + } + + /** + * Ends the registration of this protocol provider with the current + * registration service. + * + * @throws OperationFailedException with the corresponding code it the + * registration fails for some reason (e.g. a networking error or an + * implementation problem). + */ + public void unregister() + throws OperationFailedException + { + RegistrationState oldState = currentRegistrationState; + currentRegistrationState = RegistrationState.UNREGISTERED; + + fireRegistrationStateChanged( + oldState + , currentRegistrationState + , RegistrationStateChangeEvent.REASON_USER_REQUEST + , null); + } + + /** + * Returns the rss protocol icon. + * @return the rss protocol icon + */ + public ProtocolIcon getProtocolIcon() + { + return rssIcon; + } + + /** + * Returns the IM set + * @return the IM set + */ + public OperationSetBasicInstantMessagingRssImpl getBasicInstantMessaging() + { + return this.basicInstantMessaging; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/RssAccountID.java b/src/net/java/sip/communicator/impl/protocol/rss/RssAccountID.java new file mode 100644 index 0000000..93765be --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/RssAccountID.java @@ -0,0 +1,32 @@ +/* + * 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.rss; + +import net.java.sip.communicator.service.protocol.*; +import java.util.Map; + +/** + * The Rss implementation of a sip-communicator account id. + * @author Emil Ivov + */ +public class RssAccountID + extends AccountID +{ + /** + * Creates an account id from the specified id and account properties. + * + * @param userID the user identifier correspnding to the account + * @param accountProperties any other properties necessary for the account. + */ + RssAccountID(String userID, Map accountProperties) + { + super( userID, + accountProperties, + "Rss", + "rss.org"); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/RssActivator.java b/src/net/java/sip/communicator/impl/protocol/rss/RssActivator.java new file mode 100644 index 0000000..973e28d --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/RssActivator.java @@ -0,0 +1,116 @@ +/* + * 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.rss; + +import org.osgi.framework.*; +import net.java.sip.communicator.util.*; +import java.util.*; +import net.java.sip.communicator.service.protocol.*; + +import net.java.sip.communicator.impl.gui.*; +/** + * Loads the Rss provider factory and registers its services in the OSGI + * bundle context. + * + * @author Emil Ivov + */ +public class RssActivator + implements BundleActivator +{ + private static final Logger logger + = Logger.getLogger(RssActivator.class); + + /** + * A reference to the registration of our Rss protocol provider + * factory. + */ + private ServiceRegistration rssPpFactoryServReg = null; + + /** + * A reference to the Rss protocol provider factory. + */ + private static ProtocolProviderFactoryRssImpl + rssProviderFactory = null; + + /** + * The currently valid bundle context. + */ + private static BundleContext bundleContext = null; + + + /** + * Called when this bundle is started. In here we'll export the + * rss ProtocolProviderFactory implementation so that it could be + * possible to register accounts with it in SIP Communicator. + * + * @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, "Rss"); + + rssProviderFactory = new ProtocolProviderFactoryRssImpl(); + + //load all stored Rss accounts. + rssProviderFactory.loadStoredAccounts(); + + //reg the rss provider factory. + rssPpFactoryServReg = context.registerService( + ProtocolProviderFactory.class.getName(), + rssProviderFactory, + hashtable); + + logger.info("Rss protocol implementation [STARTED]."); + } + + /** + * 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>ProtocolProviderFactoryJabberImpl</tt> + * instance that we have registered from this package. + */ + public static ProtocolProviderFactoryRssImpl getProtocolProviderFactory() + { + return rssProviderFactory; + } + + /** + * 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 + { + this.rssProviderFactory.stop(); + rssPpFactoryServReg.unregister(); + logger.info("Rss protocol implementation [STOPPED]."); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/RssFeedReader.java b/src/net/java/sip/communicator/impl/protocol/rss/RssFeedReader.java new file mode 100644 index 0000000..f955045 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/RssFeedReader.java @@ -0,0 +1,256 @@ +/* + * 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.rss; + +import java.io.*; +import java.net.*; +import java.util.*; + +import com.sun.syndication.feed.synd.*; +import com.sun.syndication.io.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; + +/** + * The class used for using the Informa Library into the RSS protocol + * + * @author Jean-Albert Vescovo + */ +public class RssFeedReader +{ + + /** + * The id of the contact/feed, used to make a tcp query toward + * the .xml file containing the items of the feed. + */ + private String address; + + /** + * The title of the feed, which will be used as the displayname + * of the contact/feed. + */ + private String title; + + /** + * The object charged to retrieve the feed incoming from the relavant server. + */ + private SyndFeed feed; + + /** + * The last update date of this feed. + */ + private Date ultimateItemDate = null; + + /** + * An array of SyndEntry which will contain all the items retrieved from the feed. + */ + private SyndEntry[] items; + + /** + * Creates an instance of a rss feed with the specified string used + * as an url to contact the relevant server. + * + * @param address the url of this feed. + */ + public RssFeedReader(String address) + { + this.address = address; + this.feed = null; + this.title = "No feed avalaible !"; + } + + /** + * To refresh this rss contact/feed registered as contact + * Moreover, we sort the items by reverse chronological order after + * insert them into an Array + */ + public void recupFlux() + { + try + { + URL rssURL = new URL(this.address); + + //the most important thing in this protocol: we parse the rss feed + //using the Rome library + SyndFeedInput input = new SyndFeedInput(); + this.feed = input.build(new XmlReader(rssURL)); + this.title = this.feed.getTitle(); + + //we retrieve the items and sort them by reverse chronological order + items = (SyndEntry[])(this.feed.getEntries().toArray(new SyndEntry[0])); + sortItems(); + + //we retrieve the date of the most recent item + this.ultimateItemDate = findUltimateItemDate(); + } + catch(Exception ex) + { + ex.printStackTrace(); + System.out.println("ERROR: "+ex.getMessage()); + } + } + + /** + * Returns a String containing the message to send to the user after + * a successful query on a rss server: + * + * - if we have no items, we return "No items found on this feed !" + * - if we can't read a date in these items, we return the last 10 items of the feed + * - if we can read a date, we just return the items which have a date earlier than + * the lastQueryDate, and "No new articles in your feed since last update." if it isn't + * new item since lastQueryDate. + * + * We signal to the user ("Send anything to refresh this feed...") that he can send anything + * to refresh the present contact/feed. + * + * @param lastQueryDate the date to compare with that of the items retrieved. + * @return String string + */ + public String getPrintedFeed(Date lastQueryDate) + { + boolean more = true; + int i=0,nbNewItem = 0; + String printedFeed = new String(); + + if(items.length > 0) + { + while((i<items.length)&&more) + { + if((items[i].getPublishedDate() != null) && (lastQueryDate != null)) + { + if(items[i].getPublishedDate().compareTo(lastQueryDate)>0) + { + printedFeed += "\nAt " + items[i].getPublishedDate()+" - " + + items[i].getTitle() + + "\nLink: " + items[i].getLink() + "\n\n"; + nbNewItem++; + } + else{ + more = false; + if(nbNewItem == 0) printedFeed += + "\n\nNo new articles in your feed since last update."; + } + } + else{ + if(items[i].getPublishedDate() != null) + printedFeed += "\nAt " + items[i].getPublishedDate(); + + printedFeed += "\n" + items[i].getTitle() + + "\nLink: "+items[i].getLink()+"\n\n"; + + if(i == 10) more = false; + } + i++; + } + printedFeed += ("\n\nSend anything to refresh this feed..."); + } + else + { + printedFeed += "No items found on this feed !"; + } + return printedFeed; + } + + /** + * To sort the items retrieved from the rss contact/feed registered as contact + * We use for that a bubble sort algorithm + */ + public void sortItems() + { + int i; + int size = items.length; + SyndEntry temp; + boolean inversion; + do + { + inversion=false; + for(i = 0; i < size - 1; i++) + { + if((items[i].getPublishedDate() != null) && (items[i+1].getPublishedDate()!=null)) + if(items[i].getPublishedDate().compareTo(items[i+1].getPublishedDate())<0) + { + temp = items[i]; + items[i] = items[i+1]; + items[i+1] = temp; + inversion=true; + } + } + size--; + }while(inversion); + } + + /** + * Returns a Date that can be used to know the most recent item in a retrieved feed. + * + * @return the feed's Date representing the nearest item's date never retrieved on this feed. + */ + public Date getUltimateItemDate() + { + return this.ultimateItemDate; + } + + /** + * Returns a Date that can be used to know the most recent item in a retrieved feed. + * + * This method just gives the date of the first element of the array of ItemIF previously + * sorted. + * + * @return a Date representing the nearest item's date. + */ + private Date findUltimateItemDate() + { + if(items[0].getPublishedDate() != null) + this.ultimateItemDate = items[0].getPublishedDate(); + return this.ultimateItemDate; + } + + /** + * Returns a ChannelIF that can be used to know if a feed exists indeed. + * + * @return a ChannelIF containing the result of a query on a rss server. + */ + public SyndFeed getFeed() + { + return this.feed; + } + + /** + * Returns a Date giving the publication date of the feed on the relevant server. + * + * In most case, this date doesn't exist on the server. Not used at this time in this + * implementation. + * + * @return a Date representing the publication date of the feed. + */ + public Date getPubDate() + { + return this.feed.getPublishedDate(); + } + + /** + * Returns a String used as a displayname. + * + * @return a String title representing the feed/contact. + */ + public String getTitle() + { + return this.title; + } + + /** + * Returns a String that can be used for identifying the contact. + * + * We'll prefer to use the title of the feed as displayname. + * + * @return a String id representing and uniquely identifying the contact. + */ + public String getAddress() + { + return this.address; + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/rss/RssStatusEnum.java b/src/net/java/sip/communicator/impl/protocol/rss/RssStatusEnum.java new file mode 100644 index 0000000..fa76428 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/RssStatusEnum.java @@ -0,0 +1,104 @@ +/* + * 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.rss; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; +import java.io.*; + +/** + * An implementation of <tt>PresenceStatus</tt> that enumerates all states that + * a Rss contact can fall into. + * + * @author Jean-Albert Vescovo + */ +public class RssStatusEnum + extends PresenceStatus +{ + private static final Logger logger + = Logger.getLogger(RssStatusEnum.class); + + /** + * Indicates an Offline status or status with 0 connectivity. + */ + public static final RssStatusEnum OFFLINE + = new RssStatusEnum( + 0 + , "Offline" + , loadIcon("resources/images/rss/rss-offline.png")); + + /** + * The Online status. Indicate that the user is able and willing to + * communicate. + */ + public static final RssStatusEnum ONLINE + = new RssStatusEnum( + 65 + , "Online" + , loadIcon("resources/images/rss/rss-online.png")); + + /** + * Initialize the list of supported status states. + */ + private static List supportedStatusSet = new LinkedList(); + static + { + supportedStatusSet.add(OFFLINE); + supportedStatusSet.add(ONLINE); + } + + /** + * Creates an instance of <tt>RssPresneceStatus</tt> with the + * specified parameters. + * @param status the connectivity level of the new presence status instance + * @param statusName the name of the presence status. + * @param statusIcon the icon associated with this status + */ + private RssStatusEnum(int status, + String statusName, + byte[] statusIcon) + { + super(status, statusName, statusIcon); + } + + /** + * Returns an iterator over all status instances supproted by the rss + * provider. + * @return an <tt>Iterator</tt> over all status instances supported by the + * rss provider. + */ + static Iterator supportedStatusSet() + { + return supportedStatusSet.iterator(); + } + + /** + * Loads an image from a given image path. + * @param imagePath The path to the image resource. + * @return The image extracted from the resource at the specified path. + */ + public static byte[] loadIcon(String imagePath) + { + InputStream is = RssStatusEnum.class.getClassLoader() + .getResourceAsStream(imagePath); + + byte[] icon = null; + try + { + icon = new byte[is.available()]; + is.read(icon); + } + catch (IOException exc) + { + logger.error("Failed to load icon: " + imagePath, exc); + } + return icon; + } + +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/RssThread.java b/src/net/java/sip/communicator/impl/protocol/rss/RssThread.java new file mode 100644 index 0000000..7994764 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/RssThread.java @@ -0,0 +1,73 @@ +/* + * 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.rss; + +import java.util.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; + +/** + * Instant messaging functionalites for the Rss protocol. + * + * @author Jean-Albert Vescovo + */ +public class RssThread + extends Thread +{ + private OperationSetBasicInstantMessagingRssImpl opSet; + private ContactRssImpl rssFeed = null; + private boolean newContact = false; + private boolean aloneUpdate = false; + + /** Creates a new instance of RssThread + * @param opSet the OperationSetBasicInstantMessagingRssImpl instance that + * is managing the rss protocol. + */ + public RssThread(OperationSetBasicInstantMessagingRssImpl opSet) + { + this.opSet = opSet; + this.start(); + } + + /** Creates a new instance of RssThread + * @param opSet the OperationSetBasicInstantMessagingRssImpl instance that + * is managing the rss protocol. + * @param rssFeed the contact that the thread is going to do a query + * @param newContact newContact + * @param aloneUpdate aloneUpdate + */ + public RssThread(OperationSetBasicInstantMessagingRssImpl opSet, + ContactRssImpl rssFeed, + boolean newContact, + boolean aloneUpdate) + { + this.opSet = opSet; + this.rssFeed = rssFeed; + this.newContact = newContact; + this.aloneUpdate = aloneUpdate; + this.start(); + } + + /** + * The task executed by the thread + * If no rss contact given as parameter, the query is launched for all contacts + */ + public void run() + { + try + { + if(this.rssFeed == null) + this.opSet.refreshRssFeed(); + else + this.opSet.refreshRssFeed(this.rssFeed,this.newContact,this.aloneUpdate); + } + catch(Exception exc) + { + exc.printStackTrace(); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/rss/RssTimerRefreshFeed.java b/src/net/java/sip/communicator/impl/protocol/rss/RssTimerRefreshFeed.java new file mode 100644 index 0000000..264a893 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/RssTimerRefreshFeed.java @@ -0,0 +1,41 @@ +/* + * 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.rss; + +import java.util.TimerTask; + +/** + * Instant messaging functionalites for the Rss protocol. + * + * @author Jean-Albert Vescovo + */ + +public class RssTimerRefreshFeed + extends TimerTask +{ + private OperationSetBasicInstantMessagingRssImpl opSet; + + /** + * Creates an instance of timer used to seeking periodically the rss feeds registered + * as contacts. + * @param opSet the OperationSetBasicInstantMessagingRssImpl instance that + * is managing the rss protocol. + */ + public RssTimerRefreshFeed(OperationSetBasicInstantMessagingRssImpl opSet) + { + this.opSet = opSet; + } + + /** + * What the timer is supposed to do each time the PERIOD_REFRESH_RSS expire. + * In facts, it launch a new thread responsible for starting one or more rss queries + */ + public void run() + { + RssThread rssThr = new RssThread(this.opSet); + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/rss/rss.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/rss/rss.provider.manifest.mf new file mode 100644 index 0000000..f54a798 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/rss/rss.provider.manifest.mf @@ -0,0 +1,15 @@ +Bundle-Activator: net.java.sip.communicator.impl.protocol.rss.RssActivator +Bundle-Name: Rss Protocol Provider +Bundle-Description: A bundle providing support for the Rss protocol. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: org.osgi.framework, + org.xml.sax, + org.xml.sax.helpers, + org.jdom, + org.jdom.input, + net.java.sip.communicator.service.configuration, + net.java.sip.communicator.service.configuration.event, + net.java.sip.communicator.util, + net.java.sip.communicator.service.protocol, + net.java.sip.communicator.service.protocol.event diff --git a/src/net/java/sip/communicator/plugin/rssaccregwizz/FirstWizardPage.java b/src/net/java/sip/communicator/plugin/rssaccregwizz/FirstWizardPage.java new file mode 100644 index 0000000..49c7c44 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/rssaccregwizz/FirstWizardPage.java @@ -0,0 +1,265 @@ +/* + * 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.rssaccregwizz; + +import java.util.*; + +import java.awt.*; +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 user ID + * and the password of the account. + * + * @author Emil Ivov/Jean-Albert Vescovo + */ +public class FirstWizardPage + extends JPanel implements WizardPage, DocumentListener +{ + + public static final String FIRST_PAGE_IDENTIFIER = "FirstPageIdentifier"; + + private JPanel userPassPanel = new JPanel(new BorderLayout(10, 10)); + + private JPanel labelsPanel = new JPanel(); + + private JLabel existingAccountLabel = + new JLabel("RSS account already exists !"); + + private JLabel creatingAccountLabel = + new JLabel("Press next to creat your RSS account..."); + + private JTextField userIDField = new JTextField(); + + private JPanel mainPanel = new JPanel(); + + private Object nextPageIdentifier = WizardPage.SUMMARY_PAGE_IDENTIFIER; + + private RssAccountRegistration registration = null; + + private WizardContainer wizardContainer; + + /** + * Creates an instance of <tt>FirstWizardPage</tt>. + * @param registration the <tt>RssAccountRegistration</tt>, where + * all data through the wizard are stored + * @param wizardContainer the wizardContainer, where this page will + * be added + */ + public FirstWizardPage(RssAccountRegistration 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)); + } + + /** + * Initializes all panels, buttons, etc. + */ + private void init() + { + this.userIDField.getDocument().addDocumentListener(this); + + this.existingAccountLabel.setForeground(Color.RED); + + this.creatingAccountLabel.setForeground(Color.BLUE); + + labelsPanel.add(creatingAccountLabel); + + if(!isExistingAccount("rss")){ + labelsPanel.remove(existingAccountLabel); + labelsPanel.add(creatingAccountLabel); + setNextButtonAccordingToUserID(true); + } + else{ + labelsPanel.remove(creatingAccountLabel); + labelsPanel.add(existingAccountLabel); + setNextButtonAccordingToUserID(false); + } + + userPassPanel.add(labelsPanel, BorderLayout.CENTER); + + userPassPanel.setBorder(BorderFactory + .createTitledBorder("RSS account creation...")); + + this.add(userPassPanel, BorderLayout.CENTER); + } + + /** + * Implements the <code>WizardPage.getIdentifier</code> to return + * this page identifier. + * + * @return the Identifier of the first page in this wizard. + */ + public Object getIdentifier() + { + return FIRST_PAGE_IDENTIFIER; + } + + /** + * Implements the <code>WizardPage.getNextPageIdentifier</code> to return + * the next page identifier - the summary page. + * + * @return the identifier of the page following this one. + */ + public Object getNextPageIdentifier() + { + return nextPageIdentifier; + } + + /** + * Implements the <code>WizardPage.getBackPageIdentifier</code> to return + * the next back identifier - the default page. + * + * @return the identifier of the default wizard page. + */ + public Object getBackPageIdentifier() + { + return WizardPage.DEFAULT_PAGE_IDENTIFIER; + } + + /** + * Implements the <code>WizardPage.getWizardForm</code> to return + * this panel. + * + * @return the component to be displayed in this wizard page. + */ + public Object getWizardForm() + { + return this; + } + + /** + * Before this page is displayed enables or disables the "Next" wizard + * button according to whether the UserID field is empty. + */ + public void pageShowing() + { + if(isExistingAccount("Rss")) setNextButtonAccordingToUserID(false); + else setNextButtonAccordingToUserID(true); + } + + /** + * Saves the user input when the "Next" wizard buttons is clicked. + */ + public void pageNext() + { + nextPageIdentifier = SUMMARY_PAGE_IDENTIFIER; + userPassPanel.remove(existingAccountLabel); + registration.setUserID("Rss"); + registration.setPassword("rss"); + } + + /** + * Enables or disables the "Next" wizard button according to whether the + * User ID field is empty. + */ + private void setNextButtonAccordingToUserID(boolean newOne) + { + if(!newOne) + { + wizardContainer.setNextFinishButtonEnabled(false); + } + else + { + wizardContainer.setNextFinishButtonEnabled(true); + } + } + + /** + * Handles the <tt>DocumentEvent</tt> triggered when user types in the + * User ID field. Enables or disables the "Next" wizard button according to + * whether the User ID field is empty. + * + * @param event the event containing the update. + */ + public void insertUpdate(DocumentEvent event) + { + } + + /** + * Handles the <tt>DocumentEvent</tt> triggered when user deletes letters + * from the UserID field. Enables or disables the "Next" wizard button + * according to whether the UserID field is empty. + * + * @param event the event containing the update. + */ + public void removeUpdate(DocumentEvent event) + { + } + + public void changedUpdate(DocumentEvent event) + { + } + + public void pageHiding() + { + } + + public void pageShown() + { + } + + public void pageBack() + { + } + + /** + * Fills the UserID 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) + { + } + + /** + * Verifies whether there is already an account installed with the same + * details as the one that the user has just entered. + * + * @param userID the name of the user that the account is registered for + * @return true if there is already an account for this userID and false + * otherwise. + */ + private boolean isExistingAccount(String userID) + { + ProtocolProviderFactory factory + = RssAccRegWizzActivator.getRssProtocolProviderFactory(); + + ArrayList registeredAccounts = factory.getRegisteredAccounts(); + + for (int i = 0; i < registeredAccounts.size(); i++) + { + AccountID accountID = (AccountID) registeredAccounts.get(i); + + if (userID.equalsIgnoreCase(accountID.getUserID())) + { + return true; + } + } + return false; + } +} diff --git a/src/net/java/sip/communicator/plugin/rssaccregwizz/Resources.java b/src/net/java/sip/communicator/plugin/rssaccregwizz/Resources.java new file mode 100644 index 0000000..cde8560 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/rssaccregwizz/Resources.java @@ -0,0 +1,95 @@ +/* + * 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.rssaccregwizz; + +import java.io.*; +import java.util.*; + +import net.java.sip.communicator.util.*; + +/** + * The Messages class manages the access to the internationalization + * properties files. + * + * @author Emil Ivov + */ +public class Resources +{ + + private static Logger log = Logger.getLogger(Resources.class); + + private static final String BUNDLE_NAME + = "net.java.sip.communicator.plugin.rssaccregwizz.resources"; + + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle + .getBundle(BUNDLE_NAME); + + public static ImageID GIBBERISH_LOGO = new ImageID("protocolIcon"); + + public static ImageID PAGE_IMAGE = new ImageID("pageImage"); + + /** + * 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 exc) + { + 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 exc) + { + log.error("Failed to load image:" + path, exc); + } + + 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/rssaccregwizz/RssAccRegWizzActivator.java b/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccRegWizzActivator.java new file mode 100644 index 0000000..b68dd37 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccRegWizzActivator.java @@ -0,0 +1,109 @@ +/* + * 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.rssaccregwizz; + +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>RssAccountRegistrationWizard</tt> in the UI Service. + * + * @author Emil Ivov + */ +public class RssAccRegWizzActivator + implements BundleActivator +{ + private static Logger logger = Logger.getLogger( + RssAccRegWizzActivator.class.getName()); + + /** + * A currently valid bundle context. + */ + public static BundleContext bundleContext; + + /** + * A currently valid reference to the configuration service. + */ + private static ConfigurationService configService; + + /** + * Starts this bundle. + * @param bc the currently valid <tt>BundleContext</tt>. + */ + public void start(BundleContext bc) + { + logger.info("Loading rss account wizard."); + + bundleContext = bc; + + ServiceReference uiServiceRef = bundleContext + .getServiceReference(UIService.class.getName()); + + UIService uiService + = (UIService) bundleContext.getService(uiServiceRef); + + AccountRegistrationWizardContainer wizardContainer + = uiService.getAccountRegWizardContainer(); + + RssAccountRegistrationWizard rssWizard + = new RssAccountRegistrationWizard(wizardContainer); + + wizardContainer.addAccountRegistrationWizard(rssWizard); + + logger.info("Rss account registration wizard [STARTED]."); + } + + /** + * 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. + */ + public void stop(BundleContext context) + { + + } + + /** + * Returns the <tt>ProtocolProviderFactory</tt> for the Rss protocol. + * @return the <tt>ProtocolProviderFactory</tt> for the Rss protocol + */ + public static ProtocolProviderFactory getRssProtocolProviderFactory() + { + + ServiceReference[] serRefs = null; + + String osgiFilter = "(" + + ProtocolProviderFactory.PROTOCOL + + "=" + "Rss" + ")"; + + try + { + serRefs = bundleContext.getServiceReferences( + ProtocolProviderFactory.class.getName(), osgiFilter); + } + catch (InvalidSyntaxException ex) + { + logger.error(ex); + } + + return (ProtocolProviderFactory) bundleContext.getService(serRefs[0]); + } + + /** + * Returns the bundleContext that we received when we were started. + * + * @return a currently valid instance of a bundleContext. + */ + public BundleContext getBundleContext() + { + return bundleContext; + } +} diff --git a/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccountRegistration.java b/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccountRegistration.java new file mode 100644 index 0000000..b815f1b --- /dev/null +++ b/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccountRegistration.java @@ -0,0 +1,82 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +/** + * The <tt>RssAccountRegistration</tt> is used to store all user input data + * through the <tt>RssAccountRegistrationWizard</tt>. + * + * @author Emil Ivov/Jean-Albert Vescovo + */ +package net.java.sip.communicator.plugin.rssaccregwizz; + +public class RssAccountRegistration +{ + private String userID; + private String password; + private boolean rememberPassword; + + /** + * Returns the User ID of the rss registration account. + * @return the User ID of the rss registration account. + */ + public String getUserID() + { + return userID; + } + + /** + * Sets the user ID of the rss registration account. + * @param userID the userID of the rss registration account. + */ + public void setUserID(String userID) + { + this.userID = userID; + } + + /** + * Returns the password of the Rss registration account. + * + * @return the password of the Rss registration account. + */ + public String getPassword() + { + return password; + } + + /** + * Sets the password of the Rss registration account. + * + * @param password the password of the Rss registration account. + */ + public void setPassword(String password) + { + this.password = password; + } + + /** + * Returns <tt>true</tt> if password has to remembered, <tt>false</tt> + * otherwise. + * + * @return <tt>true</tt> if password has to remembered, <tt>false</tt> + * otherwise. + */ + public boolean isRememberPassword() + { + return true; + } + + /** + * Sets the rememberPassword value of this Rss account registration. + * + * @param rememberPassword <tt>true</tt> if password has to remembered, + * <tt>false</tt> otherwise. + */ + public void setRememberPassword(boolean rememberPassword) + { + this.rememberPassword = true; + } + +} diff --git a/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccountRegistrationWizard.java b/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccountRegistrationWizard.java new file mode 100644 index 0000000..5c0dd77 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/rssaccregwizz/RssAccountRegistrationWizard.java @@ -0,0 +1,198 @@ +/* + * 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.rssaccregwizz; + +import java.util.*; + +import org.osgi.framework.*; +import net.java.sip.communicator.impl.gui.customcontrols.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * The <tt>RssAccountRegistrationWizard</tt> is an implementation of the + * <tt>AccountRegistrationWizard</tt> for the Rss protocol. It allows + * the user to create and configure a new Rss account. + * + * @author Emil Ivov + */ +public class RssAccountRegistrationWizard + implements AccountRegistrationWizard +{ + + /** + * The first page of the rss account registration wizard. + */ + private FirstWizardPage firstWizardPage; + + /** + * The object that we use to store details on an account that we will be + * creating. + */ + private RssAccountRegistration registration + = new RssAccountRegistration(); + + private WizardContainer wizardContainer; + + private ProtocolProviderService protocolProvider; + + private String propertiesPackage + = "net.java.sip.communicator.plugin.rssaccregwizz"; + + private boolean isModification; + + /** + * Creates an instance of <tt>RssAccountRegistrationWizard</tt>. + * @param wizardContainer the wizard container, where this wizard + * is added + */ + public RssAccountRegistrationWizard(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.GIBBERISH_LOGO); + } + + /** + * Implements the <code>AccountRegistrationWizard.getPageImage</code> method. + * Returns the image used to decorate the wizard page + * + * @return byte[] the image used to decorate the wizard page + */ + public byte[] getPageImage() + { + return Resources.getImage(Resources.PAGE_IMAGE); + } + + /** + * 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("User ID", registration.getUserID()); + + return summaryTable.entrySet().iterator(); + } + + /** + * Installs the account created through this wizard. + * @return ProtocolProviderService + */ + public ProtocolProviderService finish() + { + firstWizardPage = null; + ProtocolProviderFactory factory + = RssAccRegWizzActivator.getRssProtocolProviderFactory(); + + return this.installAccount(factory, + registration.getUserID()); + } + + /** + * Creates an account for the given user and password. + * @param providerFactory the ProtocolProviderFactory which will create + * the account + * @param user the user identifier + * @return the <tt>ProtocolProviderService</tt> for the new account. + */ + public ProtocolProviderService installAccount( + ProtocolProviderFactory providerFactory, + String user) + { + + Hashtable accountProperties = new Hashtable(); + + if (registration.isRememberPassword()) + { + accountProperties.put(ProtocolProviderFactory.PASSWORD + , registration.getPassword()); + } + + try + { + AccountID accountID = providerFactory.installAccount( + user, accountProperties); + + ServiceReference serRef = providerFactory + .getProviderForAccount(accountID); + + protocolProvider = (ProtocolProviderService) + RssAccRegWizzActivator.bundleContext + .getService(serRef); + } + catch (IllegalArgumentException exc) + { + new ErrorDialog(null, exc.getMessage(), exc).showDialog(); + } + catch (IllegalStateException exc) + { + new ErrorDialog(null, exc.getMessage(), exc).showDialog(); + } + + return protocolProvider; + } + + /** + * Fills the UserID 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; + + isModification = true; + } +} diff --git a/src/net/java/sip/communicator/plugin/rssaccregwizz/resources.properties b/src/net/java/sip/communicator/plugin/rssaccregwizz/resources.properties new file mode 100644 index 0000000..5cf3b0b --- /dev/null +++ b/src/net/java/sip/communicator/plugin/rssaccregwizz/resources.properties @@ -0,0 +1,10 @@ +protocolName=Rss +protocolDescription=Add your preferred RSS feeds into SIP Communicator ! +userID=User ID: +password=Password: +rememberPassword=Remember password +userAndPassword=Identification +existingAccount=* The account you entered is already installed. + +protocolIcon=resources/images/rss/rss-online.png +pageImage=resources/images/rss/rss64x64.png diff --git a/src/net/java/sip/communicator/plugin/rssaccregwizz/rssaccregwizz.manifest.mf b/src/net/java/sip/communicator/plugin/rssaccregwizz/rssaccregwizz.manifest.mf new file mode 100644 index 0000000..71bebc6 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/rssaccregwizz/rssaccregwizz.manifest.mf @@ -0,0 +1,30 @@ +Bundle-Activator: net.java.sip.communicator.plugin.rssaccregwizz.RssAccRegWizzActivator +Bundle-Name: Rss account registration wizard +Bundle-Description: Rss 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.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, + net.java.sip.communicator.service.browserlauncher, + 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 |