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