You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@labs.apache.org by be...@apache.org on 2008/07/23 20:49:48 UTC

svn commit: r679161 - in /labs/vysper: ./ src/main/java/org/apache/vysper/xmpp/delivery/ src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/ src/main/java/org/apache/vysper/xmpp/modules/roster/ src/main/java/org/apache/vysper/xmpp/modules/ros...

Author: berndf
Date: Wed Jul 23 11:49:48 2008
New Revision: 679161

URL: http://svn.apache.org/viewvc?rev=679161&view=rev
Log:
[vysper] improve presence workflows

Modified:
    labs/vysper/ARCHITECTURE.txt
    labs/vysper/src/main/java/org/apache/vysper/xmpp/delivery/StanzaRelayBroker.java
    labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceAvailabilityHandler.java
    labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceSubscriptionHandler.java
    labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/SubscriptionType.java
    labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/persistence/MemoryRosterManager.java
    labs/vysper/src/main/java/org/apache/vysper/xmpp/resourcebinding/ResourceState.java
    labs/vysper/src/test/java/org/apache/vysper/smack/BasicClient.java
    labs/vysper/src/test/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceHandlerTestCase.java

Modified: labs/vysper/ARCHITECTURE.txt
URL: http://svn.apache.org/viewvc/labs/vysper/ARCHITECTURE.txt?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/ARCHITECTURE.txt (original)
+++ labs/vysper/ARCHITECTURE.txt Wed Jul 23 11:49:48 2008
@@ -40,3 +40,15 @@
 
 package: o.a.v.xmpp.authorization
 
+* SEDA: staged event driven architecture
+
+available stages:
+
+reading from TCP/IP socket - XMLStreamTokenizer
+handling inbound stanzas from client - QueuedStanzaProcessor
+handling outbound stanzas to clients - DeliveringStanzaRelay
+
+missing stages:
+
+handling outbound stanzas to servers - RecordingStanzaRelay (provisorial implementation)
+handling inbound stanzas issued by self or remote servers (to go to clients or not) - missing!

Modified: labs/vysper/src/main/java/org/apache/vysper/xmpp/delivery/StanzaRelayBroker.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/main/java/org/apache/vysper/xmpp/delivery/StanzaRelayBroker.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/main/java/org/apache/vysper/xmpp/delivery/StanzaRelayBroker.java (original)
+++ labs/vysper/src/main/java/org/apache/vysper/xmpp/delivery/StanzaRelayBroker.java Wed Jul 23 11:49:48 2008
@@ -45,9 +45,9 @@
         if (receiver == null || !receiver.isNodeSet()) {
             // TODO handle by server
             
-            // TODO if received <message/> from another server to MUST be set
-            // TODO if received <presence/> from another server with no to, broadcast to subscribed entities
-            // TODO if received <iq/>/get/set with no to, see 3920bis#11.1.4
+            // TODO if received <message/> from another server 'to' MUST be set
+            // TODO if received <presence/> from another server with no 'to', broadcast to subscribed entities
+            // TODO if received <iq/>/get/set with no 'to', see 3920bis#11.1.4
             
             throw new RuntimeException("not yet implemented");
             //return;

Modified: labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceAvailabilityHandler.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceAvailabilityHandler.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceAvailabilityHandler.java (original)
+++ labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceAvailabilityHandler.java Wed Jul 23 11:49:48 2008
@@ -149,8 +149,17 @@
         if (user == null) {
             // return error stanza
         }
-        
-        if (!presenceUpdate) registry.setResourceState(user.getResource(), ResourceState.AVAILABLE);
+
+        if (!presenceUpdate) {
+            // things to be done for initial presence
+            
+            // set resource state
+            ResourceState currentState = registry.getResourceState(user.getResource());
+            if (currentState != null && currentState != ResourceState.INTERESTED) {
+                // set to AVAILABLE, but do not override INTERESTED
+                registry.setResourceState(user.getResource(), ResourceState.AVAILABLE);
+            }
+        }
         updateResourcePriority(registry, sessionContext.getInitiatingEntity(), stanza.getPrioritySafe());
 
         List<Entity> contacts = new ArrayList<Entity>();
@@ -184,17 +193,15 @@
         
         // send probes to all contacts of the current jid where
         // 'subscription' is either 'to' or 'both'
-        // and jid is not blocking inbound presence notification
+        // TODO and jid is not blocking inbound presence notification
         List<RosterItem> rosterContacts_TO = new ArrayList<RosterItem>();
-        rosterContacts_FROM.addAll(item_TO);
-        rosterContacts_FROM.addAll(item_BOTH);
+        rosterContacts_TO.addAll(item_TO);
+        rosterContacts_TO.addAll(item_BOTH);
         for (RosterItem rosterItem : rosterContacts_TO) {
             Entity contact_TO = rosterItem.getJid();
             Stanza probeStanza = buildPresenceStanza(user, contact_TO, PresenceStanzaType.PROBE, null);
             relayStanza(contact_TO, probeStanza, sessionContext);
         }
-
-        
     }
 
 	@SpecCompliant(spec = "RFC3921bis-04", section = "4.2.3")

Modified: labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceSubscriptionHandler.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceSubscriptionHandler.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceSubscriptionHandler.java (original)
+++ labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceSubscriptionHandler.java Wed Jul 23 11:49:48 2008
@@ -141,7 +141,36 @@
         return null;
     }
 
-	@SpecCompliant(spec = "RFC3921bis-04", section = "3.3.3")
+/*
+FROM: http://www.xmpp.org/internet-drafts/draft-saintandre-rfc3921bis-05.txt
+
+3.3.3.  Server Processing of Inbound Unsubscribe
+
+   When the contact's server receives the inbound unsubscribe, it MUST
+   modify the subscription state and send a roster push to the contact's
+   interested resources, where the subscription state is now either
+   "none" or "to" (see Appendix A).
+
+   CS: <iq to='juliet@example.com/balcony'
+           type='set'
+           id='a78b4q6ha467'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='romeo@example.net'
+                 subscription='none'/>
+         </query>
+       </iq>
+
+   CS: <iq to='juliet@example.com/chamber'
+           type='set'
+           id='a78b4q6ha468'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='romeo@example.net'
+                 subscription='none'/>
+         </query>
+       </iq>
+    
+ */
+    @SpecCompliant(spec = "RFC3921bis-04", section = "3.3.3")
 	private void handleInboundUnsubscription(PresenceStanza stanza, SessionContext sessionContext, ResourceRegistry registry) {
 		Entity contact = stanza.getFrom();
 		Entity user = stanza.getTo();
@@ -160,6 +189,50 @@
 		}
 	}
 
+/*
+FROM: http://www.xmpp.org/internet-drafts/draft-saintandre-rfc3921bis-05.txt
+
+3.3.2.  Server Processing of Outbound Unsubscribe
+
+   As mentioned, the user's server MUST stamp the outbound unsubscribe
+   with the bare JID <us...@domain> of the user.
+
+   US: <presence from='romeo@example.net'
+                 to='juliet@example.com'
+                 type='unsubscribe'/>
+
+   If the contact is hosted on the same server, the server MUST adhere
+   to the rules specified in the next section when processing the
+   unsubscribe.
+
+   If the contact is hosted on a different server, the user's server
+   then routes the stanza to that foreign domain in accordance with core
+   XMPP stanza processing rules.
+
+   The user's server then MUST send a roster push with the updated
+   roster item to all of the user's interested resources, where the
+   subscription state is now either "none" or "from" (see Appendix A).
+
+   US: <iq to='romeo@example.net/foo'
+           type='set'
+           id='h37h3u1bv402'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='none'/>
+         </query>
+       </iq>
+
+   US: <iq to='romeo@example.net/bar'
+           type='set'
+           id='h37h3u1bv403'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='none'/>
+           </item>
+         </query>
+       </iq>
+
+ */
 	@SpecCompliant(spec = "RFC3921bis-04", section = "3.3.2")
 	private void handleOutboundUnsubscription(PresenceStanza stanza,
                                            SessionContext sessionContext, ResourceRegistry registry) {
@@ -180,7 +253,38 @@
 
 	}
 
-	@SpecCompliant(spec = "RFC3921bis-04", section = "3.2.3")
+/*
+FROM: http://www.xmpp.org/internet-drafts/draft-saintandre-rfc3921bis-05.txt
+
+3.2.3.  Server Processing of Inbound Subscription Cancellation
+
+   When the user's server receives the inbound subscription
+   cancellation, it MUST modify the subscription state and send a roster
+   push to the user's interested resources, where the subscription state
+   is now either "none" or "from" (see Appendix A).
+
+   US: <iq to='romeo@example.net/foo'
+           type='set'
+           id='h37h3u1bv400'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='none'/>
+         </query>
+       </iq>
+
+   US: <iq to='romeo@example.net/bar'
+           type='set'
+           id='h37h3u1bv401'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='none'/>
+           </item>
+         </query>
+       </iq>
+
+
+ */
+    @SpecCompliant(spec = "RFC3921bis-04", section = "3.2.3")
 	private void handleInboundSubscriptionCancellation(PresenceStanza stanza,
                                                     SessionContext sessionContext, ResourceRegistry registry) {
 		// TODO: update roster for user
@@ -200,7 +304,50 @@
 		}
 	}
 
-	@SpecCompliant(spec = "RFC3921bis-04", section = "3.2.2")
+/*
+3.2.2.  Server Processing of Outbound Subscription Cancellation
+
+   As mentioned, the contact's server MUST stamp the outbound
+   subscription cancellation with the bare JID <co...@domain> of the
+   contact.
+
+   CS: <presence from='juliet@example.com'
+                 to='romeo@example.net'
+                 type='unsubscribed'/>
+
+   If the user is hosted on the same server, the server MUST adhere to
+   the rules specified in the next section when processing the
+   subscription cancellation.
+
+   If the user is hosted on a different server, the contact's server
+   then routes the stanza to that foreign domain in accordance with core
+   XMPP stanza processing rules.
+
+   If the user is in the contact's roster, the contact's server then
+   MUST send a roster push with the updated roster item to all of the
+   contact's interested resources, where the subscription state is now
+   either "none" or "to" (see Appendix A).
+
+   CS: <iq to='juliet@example.com/balcony'
+           type='set'
+           id='a78b4q6ha465'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='romeo@example.net'
+                 subscription='none'/>
+         </query>
+       </iq>
+
+   CS: <iq to='juliet@example.com/chamber'
+           type='set'
+           id='a78b4q6ha466'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='romeo@example.net'
+                 subscription='none'/>
+         </query>
+       </iq>
+    
+*/
+    @SpecCompliant(spec = "RFC3921bis-04", section = "3.2.2")
 	private void handleOutboundSubscriptionCancellation(PresenceStanza stanza,
                                                      SessionContext sessionContext, ResourceRegistry registry) {
 		Entity user = stanza.getFrom();
@@ -221,19 +368,89 @@
 		}
 	}
 
-	@SpecCompliant(spec = "RFC3921bis-04", section = "3.1.5")
+    
+/*
+3.1.5.  Server Processing of Outbound Subscription Approval
+
+   When the contact's client sends the subscription approval, the
+   contact's server MUST stamp the outbound stanza with the bare JID
+   <co...@domain> of the contact and route or deliver the stanza to
+   the user.
+
+   CS: <presence from='juliet@example.com'
+                 to='romeo@example.net'
+                 type='subscribed'/>
+
+   The contact's server then MUST send a roster push to all of the
+   contact's interested resources.
+
+   CS: <iq to='juliet@example.com/balcony'
+           type='set'
+           id='a78b4q6ha463'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='romeo@example.net'
+                 subscription='from'/>
+         </query>
+       </iq>
+
+   CS: <iq to='juliet@example.com/chamber'
+           type='set'
+           id='a78b4q6ha464'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='romeo@example.net'
+                 subscription='from'/>
+         </query>
+       </iq>
+
+   The contact's server MUST then also send current presence to the user
+   from each of the contact's available resources.
+
+   CS: <presence from='juliet@example.com/balcony'
+                 to='romeo@example.net'/>
+
+   CS: <presence from='juliet@example.com/chamber'
+                 to='romeo@example.net'/>
+
+   From the perspective of the contact, there now exists a subscription
+   from the user.
+
+   In order to subscribe to the user's presence, the contact would then
+   send a subscription request to the user.  (XMPP clients will often
+   automatically send the subscription request instead of requiring the
+   contact to initiate the subscription request, since it is assumed
+   that the desired end state is a mutual subscription.)  Naturally,
+   when the contact sends a subscription request to the user, the
+   subscription states will be different from those shown in the
+   foregoing examples (see Appendix A) and the roles will be reversed.
+    
+     */
+    @SpecCompliant(spec = "RFC3921bis-04", section = "3.1.5")
 	private void handleOutboundSubscriptionApproval(PresenceStanza stanza,
-                                                 SessionContext sessionContext, ResourceRegistry registry, RosterManager rosterManager) {
+                                                 SessionContext sessionContext, ResourceRegistry registry, RosterManager rosterManager) 
+    {
 		Entity user = stanza.getFrom();
 		Entity contact = stanza.getTo();
 
         Entity userBareJid = user.getBareJID();
+        Entity contactBareJid = contact.getBareJID();
 
+        // record TO (= contact is subscribed to user) relationship
+        // TODO check if BOTH is needed
+        SubscriptionType newSubscriptionType = SubscriptionType.TO;
+        RosterItem newItem = new RosterItem(userBareJid, newSubscriptionType);
+        try {
+            rosterManager.addContact(contactBareJid, newItem);
+        } catch (RosterException e) {
+            // TODO internal server error
+            // contact could not be added
+        }
+        
+        // record FROM (= contact receives presence from user) relationship
         // TODO check if BOTH is needed
-        SubscriptionType newSubscriptionType = SubscriptionType.FROM;
-        RosterItem newItem = new RosterItem(contact.getBareJID(), newSubscriptionType);
+        newSubscriptionType = SubscriptionType.FROM;
+        newItem = new RosterItem(contactBareJid, newSubscriptionType);
         try {
-            rosterManager.addContact(contact, newItem);
+            rosterManager.addContact(userBareJid, newItem);
         } catch (RosterException e) {
             // TODO internal server error
             // contact could not be added
@@ -247,7 +464,7 @@
 		for (String resource : resources) {
 			Entity userResource = new EntityImpl(user, resource);
 			Stanza push = buildRosterPushStanza(userResource,
-					sessionContext.nextSequenceValue(), contact.getBareJID(),
+					sessionContext.nextSequenceValue(), contactBareJid,
                     newSubscriptionType, null);
 
 			relayStanza(userResource, push, sessionContext);
@@ -258,13 +475,79 @@
 		resources = registry.getAvailableResources(user);
 		for (String resource : resources) {
 			Entity userResource = new EntityImpl(user, resource);
-			Stanza presence = buildPresenceStanza(userResource, contact.getBareJID(), null, null);
+			Stanza presence = buildPresenceStanza(userResource, contactBareJid, null, null);
 
 			relayStanza(contact, presence, sessionContext);
 		}
 	}
 
-	@SpecCompliant(spec = "RFC3921bis-04", section = "3.1.6")
+/*
+3.1.6.  Server Processing of Inbound Subscription Approval
+
+   When the user's server receives the subscription approval, it MUST
+   first check if the contact is in the user's roster with a
+   subscription='none' or subscription='from' and the 'ask' flag set to
+   "subscribe" (i.e., a subscription states of "None + Pending Out" or
+   "From + Pending Out"; see Appendix A).  If the contact is not in the
+   user's roster with either of those states, the user's server MUST
+   silently ignore the presence stanza of type "subscribed" (i.e., it
+
+   MUST NOT route it to the user, modify the user's roster, or generate
+   a roster push to the user's interested resources).
+
+   If the foregoing check is successful, the user's server MUST initiate
+   a roster push to all of the user's interested resources, containing
+   an updated roster item for the contact with the 'subscription'
+   attribute set to a value of "to".
+
+   US: <iq to='romeo@example.net/foo'
+           type='set'
+           id='b89c5r7ib576'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='to'/>
+         </query>
+       </iq>
+
+   US: <iq to='romeo@example.net/bar'
+           type='set'
+           id='b89c5r7ib577'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='to'/>
+           </item>
+         </query>
+       </iq>
+
+   From the perspective of the user, there now exists a subscription to
+   the contact's presence.
+
+   The user's server MUST also deliver the available presence stanza
+   received from each of the contact's available resources to each of
+   the user's available resources.
+
+   [ ... to resource1 ... ]
+
+   US: <presence from='juliet@example.com/balcony'
+                 to='romeo@example.net'/>
+
+   [ ... to resource2 ... ]
+
+   US: <presence from='juliet@example.com/balcony'
+                 to='romeo@example.net'/>
+
+   [ ... to resource1 ... ]
+
+   US: <presence from='juliet@example.com/chamber'
+                 to='romeo@example.net'/>
+
+   [ ... to resource2 ... ]
+
+   US: <presence from='juliet@example.com/chamber'
+                 to='romeo@example.net'/>
+    
+*/
+    @SpecCompliant(spec = "RFC3921bis-04", section = "3.1.6")
 	private Stanza handleInboundSubscriptionApproval(PresenceStanza stanza,
                                                   SessionContext sessionContext, ResourceRegistry registry) {
 
@@ -288,7 +571,75 @@
 		return null;
 	}
 
-	@SpecCompliant(spec = "RFC3920bis-04", section = "3.1.3")
+/*
+3.1.3.  Server Processing of Inbound Subscription Request
+
+   The contact's server MUST adhere to the following rules when
+   processing the inbound subscription request:
+
+   1.  Above all, the contact's server MUST NOT automatically approve
+       subscription requests on the contact's behalf; instead, if a
+       subscription request requires approval then the contact's server
+       MUST deliver that request to the contact's available resource(s)
+       for approval or denial by the contact.
+   2.  If the contact does not exist, the contact's server MUST
+       automatically return a presence stanza of type "unsubscribed" to
+       the user.
+
+
+   CS: <presence from='juliet@example.com'
+                 to='romeo@example.net'
+                 type='unsubscribed'/>
+
+   3.  If the contact exists and the user already has a subscription to
+       the user's presence, then the contact's server SHOULD auto-reply
+       on behalf of the contact by sending a presence stanza of type
+       "subscribed" from the contact's bare JID to the user's bare JID.
+       If the contact previously sent a presence stanza of type
+       "subscribed" and the contact's server treated that as indicating
+       "pre-approval" for the user's presence subscription (see
+       Appendix A), then the contact's server MAY also auto-reply on
+       behalf of the contact.
+   4.  If the contact exists, the user does not already have a
+       subscription to the contact's presence, and there is at least one
+       available resource associated with the contact when the
+       subscription request is received by the contact's server, the
+       contact's server MUST broadcast that subscription request to all
+       available resources in accordance with Server Rules for
+       Processing XML Stanzas (Section 8).
+   5.  If the contact exists, the user does not already have a
+       subscription to the contact's presence, and the contact has no
+       available resources when the subscription request is received by
+       the contact's server, the contact's server MUST keep a record of
+       the complete presence stanza comprising the subscription request,
+       including any extended content contained therein, and deliver the
+       request when the contact next has an available resource.  The
+       contact's server MUST continue to deliver the subscription
+       request whenever the contact creates an available resource, until
+       the contact either approves or denies the request.  (Note: The
+       contact's server MUST NOT deliver more than one subscription
+       request from any given user when the contact next has an
+       available resource; e.g., if the user sends multiple subscription
+       requests to the contact while the contact is offline, the
+       contact's server SHOULD store only one of those requests, such as
+       the first request or last request, and MUST deliver only one of
+       the requests when the contact next has an available resource;
+       this helps to prevent "subscription request spam".)
+
+   Note: If the subscription request is directed to a full JID
+   <contact@domain/resource> instead of a bare JID <co...@domain>, the
+   contact's server SHOULD treat it as if the request had been directed
+   to the contact's bare JID.  This simplifies processing of presence
+   subscriptions.
+
+   Note: Until and unless the contact approves the subscription request
+   as described under Section 3.1.4, the contact's server MUST NOT add
+   an item for the user to the contact's roster.
+
+
+    
+     */
+    @SpecCompliant(spec = "RFC3920bis-04", section = "3.1.3")
 	private Stanza handleInboundSubscriptionRequest(PresenceStanza stanza,
                                                  SessionContext sessionContext, ResourceRegistry registry, RosterManager rosterManager) {
 		Entity contact = stanza.getFrom();
@@ -317,7 +668,7 @@
 		// user exists and doesn't have a subscription
 		// to the contact and user is currently online (# of
 		// interested resources > 0)
-		List<String> interestedResources = registry.getInterestedOrAvailableResources(user);
+		List<String> interestedResources = registry.getInterestedResources(user);
 		if (!interestedResources.isEmpty()) {
 			for (String resource : interestedResources) {
 				Entity userResource = new EntityImpl(user, resource);
@@ -333,13 +684,79 @@
 		return null;
 	}
 
+/*
+FROM: http://www.xmpp.org/internet-drafts/draft-saintandre-rfc3921bis-05.txt
+
+3.1.2.  Server Processing of Outbound Subscription Request
+
+   As mentioned, the user's server MUST stamp the outbound subscription
+   request with the bare JID <us...@domain> of the user.
+
+   US: <presence from='romeo@example.net'
+                 to='juliet@example.com'
+                 type='subscribe'/>
+
+   Note: If the subscription request is directed to a full JID
+   <contact@domain/resource> instead of a bare JID <co...@domain>, the
+   user's server SHOULD treat it as if the request had been directed to
+   the contact's bare JID and modify the 'to' address accordingly.  This
+   simplifies processing of presence subscriptions.
+
+   If the potential contact is hosted on the same server, the server
+   MUST adhere to the rules specified in the next section in processing
+   the subscription request and delivering it to the (local) contact.
+
+   If the potential contact is hosted on a different server, the user's
+   server then routes the stanza to that foreign domain in accordance
+   with core XMPP stanza processing rules.
+
+   The user's server MUST then send a roster push to all of the user's
+   interested resources, containing the potential contact with a
+   subscription state of "none" and with notation that the subscription
+   is pending (via an 'ask' attribute whose value is "subscribe").
+
+   US: <iq to='romeo@example.net/foo'
+           type='set'
+           id='b89c5r7ib574'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='none'
+                 ask='subscribe'/>
+         </query>
+       </iq>
+
+   US: <iq to='romeo@example.net/bar'
+           type='set'
+           id='b89c5r7ib575'>
+         <query xmlns='jabber:iq:roster'>
+           <item jid='juliet@example.com'
+                 subscription='none'
+                 ask='subscribe'/>
+           </item>
+         </query>
+       </iq>
+
+   Note: Because the server must send this roster push, a client MAY
+   simply wait for the roster push rather than proactively adding the
+   contact to the user's roster before sending the subscription request.
+
+   Note: If the contact does not approve or deny the subscription
+   request within some configurable amount of time, the user's server
+   SHOULD re-send the subscription request to the contact based on an
+   implementation-specific algorithm (e.g., whenever a new resource
+   becomes available for the user, or after a certain amount of time has
+   elapsed); this helps to recover from transient, silent errors that
+   may have occurred in relation to the original subscription request.
+    
+*/
+
 	@SpecCompliant(spec = "RFC3920bis-04", section = "3.1.2")
 	private void handleOutboundSubscriptionRequest(PresenceStanza stanza,
                                                 SessionContext sessionContext, ResourceRegistry registry) {
 		ServerRuntimeContext serverRuntimeContext = sessionContext.getServerRuntimeContext();
 		StanzaRelay stanzaRelay = serverRuntimeContext.getStanzaRelay();
 
-		// relay the stanza to the contact's server
+		// relay the stanza to the contact (via the contact's server)
 		try {
 			stanzaRelay.relay(stanza.getTo(), stanza, new IgnoreFailureStrategy());
 		} catch (DeliveryException e) {
@@ -350,12 +767,12 @@
 		Entity contact = stanza.getTo();
 
 		// send roster push to all of the user's interested resources
-		List<String> resources = registry.getInterestedOrAvailableResources(user);
+		List<String> resources = registry.getInterestedResources(user);
 		for (String resource : resources) {
 			Entity userResource = new EntityImpl(user, resource);
 			Stanza push = buildRosterPushStanza(userResource,
 					sessionContext.nextSequenceValue(), contact.getBareJID(),
-                    SubscriptionType.FROM, AskSubscriptionType.ASK_SUBSCRIBE);
+                    SubscriptionType.TO, AskSubscriptionType.ASK_SUBSCRIBE);
 			try {
 				stanzaRelay.relay(userResource, push, new IgnoreFailureStrategy());
 			} catch (DeliveryException e) {

Modified: labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/SubscriptionType.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/SubscriptionType.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/SubscriptionType.java (original)
+++ labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/SubscriptionType.java Wed Jul 23 11:49:48 2008
@@ -17,13 +17,26 @@
 package org.apache.vysper.xmpp.modules.roster;
 
 /**
+ * see http://www.xmpp.org/internet-drafts/draft-saintandre-rfc3921bis-05.html#roster-syntax-subscription
+ * "none" -- the user does not have a subscription to the contact's presence, and the contact does 
+ *    not have a subscription to the user's presence
+ * "to" -- the user has a subscription to the contact's presence, but the contact does not have a 
+ *    subscription to the user's presence
+ * "from" -- the contact has a subscription to the user's presence, but the user does not have a 
+ *    subscription to the contact's presence
+ * "both" -- both the user and the contact have subscriptions to each other's presence (also called 
+ *    a "mutual subscription")
+ * 
+ * remove is a special case:
+ * In a roster set, the value of the 'subscription' attribute MAY be "remove", which indicates that the item is to be 
+ * removed from the roster; a receiving server MUST ignore all values of the 'subscription' attribute other than "remove".
  */
 public enum SubscriptionType {
 
     BOTH("both"),  
     FROM("from"),  
     NONE("none"),  
-    REMOVE("remove"),  
+    REMOVE("remove"),   
     TO("to");  
 
     private final String value;
@@ -36,4 +49,51 @@
         return value;
     }
 
+    public boolean includesFrom() {
+        return this == FROM || this == BOTH;
+    }
+    
+    public boolean includesTo() {
+        return this == TO || this == BOTH;
+    }
+    
+    public boolean acceptsTo() {
+        return this == NONE || this == FROM;
+    }
+    
+    public boolean acceptsFrom() {
+        return this == NONE || this == TO;
+    }
+    
+    public static SubscriptionType addState(SubscriptionType old, SubscriptionType add) {
+        switch (add) {
+
+            case BOTH:
+                throw new RuntimeException("add 'both' not valid");
+                
+            case FROM:
+                if (!old.acceptsFrom()) throw new RuntimeException("cannot add " + add.value() + " to " + old.value());
+                if (old == NONE) return FROM;
+                if (old == TO) return BOTH; 
+                throw new RuntimeException("add FROM not supported for " + old.value());
+                
+            case NONE:
+                return add;
+            
+            case REMOVE:
+                throw new RuntimeException("add 'remove' not valid");
+                
+            case TO:
+                if (!old.acceptsTo()) throw new RuntimeException("cannot add " + add.value() + " to " + old.value()); 
+                if (old == NONE) return TO;
+                if (old == FROM) return BOTH; 
+                throw new RuntimeException("add TO not supported for " + old.value());
+                
+            default:
+                throw new RuntimeException("not implemented: adding " + add.value());
+                
+        }
+    }
+    
+
 }

Modified: labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/persistence/MemoryRosterManager.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/persistence/MemoryRosterManager.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/persistence/MemoryRosterManager.java (original)
+++ labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/roster/persistence/MemoryRosterManager.java Wed Jul 23 11:49:48 2008
@@ -35,6 +35,7 @@
     
 
     public Roster retrieve(Entity jid) {
+        jid = jid.getBareJID();
         if (!rosterMap.containsKey(jid)) rosterMap.put(jid, new MutableRoster());
         return rosterMap.get(jid);
     }
@@ -44,7 +45,7 @@
         MutableRoster mutableRoster = (MutableRoster) retrieve(jid);
         if (mutableRoster == null) {
             mutableRoster = new MutableRoster();
-            rosterMap.put(jid, mutableRoster);
+            rosterMap.put(jid.getBareJID(), mutableRoster);
         }
         mutableRoster.addItem(rosterItem);
     }

Modified: labs/vysper/src/main/java/org/apache/vysper/xmpp/resourcebinding/ResourceState.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/main/java/org/apache/vysper/xmpp/resourcebinding/ResourceState.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/main/java/org/apache/vysper/xmpp/resourcebinding/ResourceState.java (original)
+++ labs/vysper/src/main/java/org/apache/vysper/xmpp/resourcebinding/ResourceState.java Wed Jul 23 11:49:48 2008
@@ -14,16 +14,16 @@
 	 */
 	CONNECTED,
     /**
+    * A connected resource is considered "available" after successfully sending
+      * its initial presence.
+      */
+    AVAILABLE,
+    /**
      * An available resource is considered "interested" after requesting the
      * entity's roster.
      */
     INTERESTED,
     /**
-      * A connected resource is considered "available" after successfully sending
-      * its initial presence.
-      */
-    AVAILABLE,
-    /**
       * A resource is no longer "available" 
       */
     UNAVAILABLE

Modified: labs/vysper/src/test/java/org/apache/vysper/smack/BasicClient.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/test/java/org/apache/vysper/smack/BasicClient.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/test/java/org/apache/vysper/smack/BasicClient.java (original)
+++ labs/vysper/src/test/java/org/apache/vysper/smack/BasicClient.java Wed Jul 23 11:49:48 2008
@@ -55,20 +55,22 @@
 
 //            if (!saslAuthentication.isAuthenticated()) return;
 
-            String toEntity = to + "@vysper.org";
-            
+
             connection.login(me + "@vysper.org", "password1");
 
             connection.getRoster().setSubscriptionMode(Roster.SubscriptionMode.accept_all);
 
-            Presence presence = new Presence(Presence.Type.subscribe);
-            presence.setTo(toEntity);
-            connection.sendPacket(presence);
-
-            Chat chat = connection.getChatManager().createChat(toEntity, new MessageListener() {
-                public void processMessage(Chat chat, Message message) { 
-                    System.out.println("log received message: " + message.getBody()); } 
-                 });
+            if (to != null) {
+                Presence presence = new Presence(Presence.Type.subscribe);
+                String toEntity = to + "@vysper.org";
+                presence.setTo(toEntity);
+                connection.sendPacket(presence);
+    
+                Chat chat = connection.getChatManager().createChat(toEntity, new MessageListener() {
+                    public void processMessage(Chat chat, Message message) { 
+                        System.out.println("log received message: " + message.getBody()); } 
+                     });
+            }
             
             connection.sendPacket(new Presence(Presence.Type.available, "pommes", 1, Presence.Mode.available));
             

Modified: labs/vysper/src/test/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceHandlerTestCase.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/test/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceHandlerTestCase.java?rev=679161&r1=679160&r2=679161&view=diff
==============================================================================
--- labs/vysper/src/test/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceHandlerTestCase.java (original)
+++ labs/vysper/src/test/java/org/apache/vysper/xmpp/modules/core/im/handler/PresenceHandlerTestCase.java Wed Jul 23 11:49:48 2008
@@ -18,18 +18,21 @@
 package org.apache.vysper.xmpp.modules.core.im.handler;
 
 import junit.framework.TestCase;
-import org.apache.vysper.xmpp.addressing.EntityImpl;
 import org.apache.vysper.xmpp.addressing.EntityFormatException;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
 import org.apache.vysper.xmpp.delivery.StanzaReceiverQueue;
-import org.apache.vysper.xmpp.delivery.DeliveringStanzaRelay;
 import org.apache.vysper.xmpp.delivery.StanzaReceiverRelay;
-import org.apache.vysper.xmpp.resourcebinding.ResourceState;
+import org.apache.vysper.xmpp.modules.roster.RosterItem;
+import org.apache.vysper.xmpp.modules.roster.SubscriptionType;
+import org.apache.vysper.xmpp.modules.roster.persistence.MemoryRosterManager;
 import org.apache.vysper.xmpp.resourcebinding.BindException;
+import org.apache.vysper.xmpp.resourcebinding.ResourceState;
 import org.apache.vysper.xmpp.server.TestSessionContext;
+import org.apache.vysper.xmpp.stanza.Stanza;
 import org.apache.vysper.xmpp.stanza.StanzaBuilder;
 import org.apache.vysper.xmpp.stanza.XMPPCoreStanza;
-import org.apache.vysper.xmpp.stanza.Stanza;
-import org.apache.vysper.xmpp.modules.roster.persistence.MemoryRosterManager;
+import org.apache.vysper.xmpp.stanza.PresenceStanzaType;
+import org.apache.vysper.xmpp.xmlfragment.XMLElementVerifier;
 
 /**
  */
@@ -42,18 +45,56 @@
     private PresenceHandler handler = new PresenceHandler();
     private StanzaReceiverQueue initiatorsQueue;
     private EntityImpl clientFullQualified = null;
+    protected StanzaReceiverQueue anotherInterestedResourceQueue;
+    protected StanzaReceiverQueue anotherAvailableResourceQueue;
+    protected StanzaReceiverQueue unrelatedQueue;
+    protected String anotherAvailableResourceId;
+    protected String anotherInterestedResourceId;
+    protected MemoryRosterManager rosterManager;
+    private StanzaReceiverQueue subscribed_TO_Queue;
+    private StanzaReceiverQueue subscribed_BOTH_Queue;
+    private StanzaReceiverQueue subscribed_FROM_Queue;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         sessionContext = TestSessionContext.createWithStanzaReceiverRelayAuthenticated();
-        sessionContext.getServerRuntimeContext().registerServerRuntimeContextService(new MemoryRosterManager());
+        rosterManager = new MemoryRosterManager();
+        sessionContext.getServerRuntimeContext().registerServerRuntimeContextService(rosterManager);
+
         client = EntityImpl.parse("tester@vysper.org");
         sessionContext.setInitiatingEntity(client);
         boundResourceId = sessionContext.bindResource();
         initiatorsQueue = sessionContext.addReceiver(client, boundResourceId);
         setResourceState(boundResourceId, ResourceState.INTERESTED);
         clientFullQualified = new EntityImpl(client, boundResourceId);
+
+        // set up more resources for the same session
+        anotherInterestedResourceId = sessionContext.bindResource();
+        anotherInterestedResourceQueue = sessionContext.addReceiver(client, anotherInterestedResourceId);
+        anotherAvailableResourceId = sessionContext.bindResource();
+        anotherAvailableResourceQueue = sessionContext.addReceiver(client, anotherAvailableResourceId);
+        setResourceState(anotherAvailableResourceId, ResourceState.AVAILABLE);
+        
+        // set up completely unrelated resource
+        unrelatedQueue = sessionContext.addReceiver(EntityImpl.parse("unrelated@vysper.org"), null);
+
+        // now we have: 
+        // 3 sessions for the same entity: one initiating, one interested (not yet avail), one available
+        // and another unrelated resource
+
+        EntityImpl subscribed_TO = EntityImpl.parse("subscribed_to@vysper.org");
+        rosterManager.addContact(client, new RosterItem(subscribed_TO, SubscriptionType.TO));
+        subscribed_TO_Queue = sessionContext.addReceiver(subscribed_TO, null);
+
+        EntityImpl subscribed_BOTH = EntityImpl.parse("subscribed_both@vysper.org");
+        rosterManager.addContact(client, new RosterItem(subscribed_BOTH, SubscriptionType.BOTH));
+        subscribed_BOTH_Queue = sessionContext.addReceiver(subscribed_BOTH, null);
+
+        EntityImpl subscribed_FROM = EntityImpl.parse("subscribed_from@vysper.org");
+        rosterManager.addContact(client, new RosterItem(subscribed_FROM, SubscriptionType.FROM));
+        subscribed_FROM_Queue = sessionContext.addReceiver(subscribed_FROM, null);
+        
     }
 
     private void setResourceState(String resourceId, ResourceState state) {
@@ -63,42 +104,45 @@
     public void testInitialPresence() throws BindException, EntityFormatException {
         XMPPCoreStanza initialPresence = XMPPCoreStanza.getWrapper(StanzaBuilder.createPresenceStanza(null, null, null, null, null, null).getFinalStanza());
 
-        String anotherInterestedResourceId = sessionContext.bindResource();
-        StanzaReceiverQueue anotherInterestedResourceQueue = sessionContext.addReceiver(client, anotherInterestedResourceId);
-        String anotherAvailableResourceId = sessionContext.bindResource();
-        StanzaReceiverQueue anotherAvailableResourceQueue = sessionContext.addReceiver(client, anotherAvailableResourceId);
-        setResourceState(anotherAvailableResourceId, ResourceState.AVAILABLE);
-        StanzaReceiverQueue unrelatedQueue = sessionContext.addReceiver(EntityImpl.parse("unrelated@vysper.org"), null);
-
-        // now we have: 
-        // 3 sessions for the same entity: one initiating, one interested (not yet avail), one available
-
         assertEquals(ResourceState.INTERESTED, getResourceState());
         handler.executeCore(initialPresence, sessionContext);
-        // check resource state change
-        assertEquals(ResourceState.AVAILABLE, getResourceState());
+        // check resource state change, do not override interested
+        assertEquals(ResourceState.INTERESTED, getResourceState());
 
-        assertEquals(2, ((StanzaReceiverRelay) sessionContext.getServerRuntimeContext().getStanzaRelay()).getCountDelivered());
+        assertEquals(6, ((StanzaReceiverRelay) sessionContext.getServerRuntimeContext().getStanzaRelay()).getCountDelivered());
         
-        assertNull(anotherInterestedResourceQueue.getNext()); // does not sent pres to un-available
-        assertNull(unrelatedQueue.getNext()); // does not sent pres to everybody arbitrarily
-
         Stanza initiatorNotification = initiatorsQueue.getNext();
         assertNotNull(initiatorNotification);
-        assertTrue(initiatorNotification.getVerifier().nameEquals("presence"));
+        assertTrue(checkPresence(initiatorNotification, null));
         assertTrue(initiatorNotification.getVerifier().fromAttributeEquals(clientFullQualified.getFullQualifiedName()));
         assertTrue(initiatorNotification.getVerifier().toAttributeEquals(clientFullQualified.getFullQualifiedName()));
         
         Stanza availableResourceNotification = anotherAvailableResourceQueue.getNext();
         assertNotNull(availableResourceNotification);
-        assertTrue(availableResourceNotification.getVerifier().nameEquals("presence"));
+        assertTrue(checkPresence(availableResourceNotification, null));
         assertTrue(availableResourceNotification.getVerifier().fromAttributeEquals(clientFullQualified.getFullQualifiedName()));
         assertTrue(availableResourceNotification.getVerifier().toAttributeEquals(client.getFullQualifiedName() + "/" + anotherAvailableResourceId));
         
-        // TODO test TO contacts recv pres, too
-        
-        // TODO test presence probes
-        
+        assertNull(anotherInterestedResourceQueue.getNext()); // does not sent pres to un-available
+        assertNull(unrelatedQueue.getNext()); // does not sent pres to everybody arbitrarily
+        assertTrue(checkPresence(subscribed_FROM_Queue.getNext(), null)); // pres sent to FROM contacts
+        assertNull(subscribed_FROM_Queue.getNext()); // no second stanza sent to FROMs 
+
+        // initial pres and pres probe might come in different order
+        assertTrue(checkPresence(subscribed_BOTH_Queue.getNext(), null)); // pres sent to BOTH contacts
+        assertTrue(checkPresence(subscribed_BOTH_Queue.getNext(), PresenceStanzaType.PROBE)); // probe sent to BOTH contacts
+        assertNull(subscribed_BOTH_Queue.getNext()); // no third stanza sent to BOTHs 
+
+        assertTrue(checkPresence(subscribed_TO_Queue.getNext(), PresenceStanzaType.PROBE)); // probe sent to TO contacts
+        assertNull(subscribed_TO_Queue.getNext()); // pres NOT sent to TO contacts
+    }
+
+    private boolean checkPresence(Stanza stanza, PresenceStanzaType presenceType) {
+        XMLElementVerifier xmlElementVerifier = stanza.getVerifier();
+        if (!xmlElementVerifier.nameEquals("presence")) return false;
+        if (presenceType == null && xmlElementVerifier.attributePresent("type")) return false;
+        if (presenceType != null && !xmlElementVerifier.attributeEquals("type", presenceType.value())) return false;
+        return true;
     }
 
     private ResourceState getResourceState() {



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@labs.apache.org
For additional commands, e-mail: commits-help@labs.apache.org