You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@labs.apache.org by gs...@apache.org on 2008/05/01 15:15:31 UTC
svn commit: r652509 -
/labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/PresenceHandler.java
Author: gseitz
Date: Thu May 1 06:15:31 2008
New Revision: 652509
URL: http://svn.apache.org/viewvc?rev=652509&view=rev
Log:
[vysper] LABS-101: first take on handling presence subscription request/approval/cancellation and unsubscription
Modified:
labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/PresenceHandler.java
Modified: labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/PresenceHandler.java
URL: http://svn.apache.org/viewvc/labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/PresenceHandler.java?rev=652509&r1=652508&r2=652509&view=diff
==============================================================================
--- labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/PresenceHandler.java (original)
+++ labs/vysper/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/PresenceHandler.java Thu May 1 06:15:31 2008
@@ -17,24 +17,448 @@
package org.apache.vysper.xmpp.modules.core.base.handler;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.vysper.compliance.SpecCompliant;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.delivery.DeliveryException;
+import org.apache.vysper.xmpp.delivery.StanzaRelay;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.resourcebinding.ResourceRegistry;
+import org.apache.vysper.xmpp.resourcebinding.ResourceState;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
import org.apache.vysper.xmpp.server.SessionContext;
import org.apache.vysper.xmpp.stanza.PresenceStanza;
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.xmlfragment.XMLElementVerifier;
/**
* handling presence stanzas
*/
public class PresenceHandler extends XMPPCoreStanzaHandler {
- public String getName() {
- return "presence";
- }
-
- protected boolean verifyType(Stanza stanza) {
- return PresenceStanza.isOfType(stanza);
- }
-
- protected Stanza executeCore(XMPPCoreStanza stanza, SessionContext sessionContext) {
- return null;
- }
+ public String getName() {
+ return "presence";
+ }
+
+ protected boolean verifyType(Stanza stanza) {
+ return PresenceStanza.isOfType(stanza);
+ }
+
+ protected Stanza executeCore(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ // TODO: either use the resource associated with the session
+ // (initiatingEntity)
+ // or in case of multiple resources, use the from attribute or return an
+ // error if the from attribute is not present.
+ Entity initiatingEntity = sessionContext.getInitiatingEntity();
+ XMLElementVerifier verifier = stanza.getVerifier();
+
+ int nrOfAttributes = stanza.getAttributes().size();
+ if ((nrOfAttributes == 0 || verifier.onlyAttributesPresent("from"))
+ && verifier.subElementsPresentExact(0)) {
+ // RFC3921bis-04#4.2.2 Initial Presence
+ handleOutboundInitialPresence(stanza, sessionContext,
+ initiatingEntity);
+ } else if (verifier.onlyAttributesPresent("type", "to")
+ && sessionContext.isServerToServer() == false) {
+ // this is an outbound subscription
+ // request/approval/cancellation/unsubscription
+ // stamp it with the bare JID of the user
+ Entity user = initiatingEntity;
+ XMPPCoreStanza stampedStanza = buildPresenceStanza(user
+ .getBareJID(), stanza.getTo().getBareJID(), stanza
+ .getType());
+ String type = stanza.getType();
+ if ("subscribe".equals(type)) {
+ // RFC3921bis-04#3.1.2
+ handleOutboundSubscriptionRequest(stampedStanza, sessionContext);
+ } else if ("unsubscribe".equals(type)) {
+ // RFC3921bis-04#3.3.2
+ handleOutboundUnsubscription(stampedStanza, sessionContext);
+ } else if ("subscribed".equals(type)) {
+ // RFC3921bis-04#3.1.5
+ handleOutboundSubscriptionApproval(stampedStanza,
+ sessionContext);
+ } else if ("unsubscribed".equals(type)) {
+ // RFC3921bis-04#3.2.2
+ handleOutboundSubscriptionCancellation(stampedStanza,
+ sessionContext);
+ }
+ } else if (verifier.onlyAttributesPresent("from", "to")) {
+ // RFC3921bis-04#4.2.3
+ handleInboundInitialPresence(stanza, sessionContext);
+ } else if (verifier.onlyAttributesPresent("type", "to", "from")) {
+ String type = stanza.getType();
+ if ("subscribe".equals(type)) {
+ // RFC3921bis-04#3.1.3
+ return handleInboundSubscriptionRequest(stanza, sessionContext);
+ } else if ("subscribed".equals(type)) {
+ // RFC3921bis-04#3.1.6
+ return handleInboundSubscriptionApproval(stanza, sessionContext);
+ } else if ("unsubscribed".equals(type)) {
+ // RFC3921bis-04#3.2.3
+ handleInboundSubscriptionCancellation(stanza, sessionContext);
+ } else if ("unsubscribe".equals(type)) {
+ // RFC3921bis-04#3.3.3
+ handleInboundUnsubscription(stanza, sessionContext);
+ } else if ("probe".equals(type)) {
+
+ }
+
+ } else if (verifier.allAttributesPresent("from", "to")) {
+
+ }
+
+ return null;
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "3.3.3")
+ private void handleInboundUnsubscription(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ Entity contact = stanza.getFrom();
+ Entity user = stanza.getTo();
+
+ // TODO: remove subscription from user to contact
+
+ List<String> resources = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry().getInterestedResources(user);
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ // TODO: determine the right subscription: 'none' or 'to'
+ Stanza push = buildRosterPushStanza(userResource
+ .getFullQualifiedName(),
+ sessionContext.nextSequenceValue(), contact.getBareJID(),
+ "???");
+
+ relayStanza(userResource, push, sessionContext);
+ }
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "3.3.2")
+ private void handleOutboundUnsubscription(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ ServerRuntimeContext serverRuntimeContext = sessionContext
+ .getServerRuntimeContext();
+ ResourceRegistry registry = serverRuntimeContext.getResourceRegistry();
+
+ Entity user = stanza.getFrom();
+ Entity contact = stanza.getTo();
+
+ relayStanza(contact, stanza, sessionContext);
+
+ List<String> resources = registry.getInterestedResources(user);
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ // TODO: determine the correct subscription state: 'none' or 'from'
+ Stanza push = buildRosterPushStanza(userResource
+ .getFullQualifiedName(),
+ sessionContext.nextSequenceValue(), contact.getBareJID(),
+ "???");
+ relayStanza(userResource, push, sessionContext);
+ }
+
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "3.2.3")
+ private void handleInboundSubscriptionCancellation(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ // TODO: update roster for user
+ // TODO: if current subscription is either 'both' or
+ ResourceRegistry registry = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry();
+
+ Entity contact = stanza.getFrom();
+ Entity user = stanza.getTo();
+
+ List<String> resources = registry.getInterestedResources(user);
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ // TODO: determine the correct subscription state
+ Stanza push = buildRosterPushStanza(userResource
+ .getFullQualifiedName(),
+ sessionContext.nextSequenceValue(), contact.getBareJID(),
+ "???");
+ relayStanza(userResource, push, sessionContext);
+ }
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "3.2.2")
+ private void handleOutboundSubscriptionCancellation(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ Entity user = stanza.getFrom();
+ Entity contact = stanza.getTo();
+
+ relayStanza(contact, stanza, sessionContext);
+
+ // send roster push to all of the user's interested resources
+ List<String> resources = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry().getInterestedResources(user);
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ // TODO: determine the right subscription: 'to' or 'none'
+ Stanza push = buildRosterPushStanza(userResource
+ .getFullQualifiedName(),
+ sessionContext.nextSequenceValue(), contact.getBareJID(),
+ "???");
+
+ relayStanza(userResource, push, sessionContext);
+ }
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "3.1.5")
+ private void handleOutboundSubscriptionApproval(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ ResourceRegistry registry = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry();
+
+ Entity user = stanza.getFrom();
+ Entity contact = stanza.getTo();
+
+ relayStanza(contact, stanza, sessionContext);
+
+ // send roster push to all of the user's interested resources
+ List<String> resources = registry.getInterestedResources(user);
+
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ Stanza push = buildRosterPushStanza(userResource
+ .getFullQualifiedName(),
+ sessionContext.nextSequenceValue(), contact.getBareJID(),
+ "from");
+
+ relayStanza(userResource, push, sessionContext);
+ }
+
+ // send presence information from user's available resource to the
+ // contact
+ resources = registry.getAvailableResources(user);
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ Stanza presence = buildPresenceStanza(userResource
+ .getFullQualifiedName(), contact.getBareJID(), null);
+
+ relayStanza(contact, presence, sessionContext);
+ }
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "3.1.6")
+ private Stanza handleInboundSubscriptionApproval(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+
+ // TODO: check if contact is in users roster with
+ // subscription="from||none" && ask="subscribe"
+ if (true /* condition is met */) {
+ Entity user = stanza.getTo();
+ Entity contact = stanza.getFrom();
+ // send roster push to all interested resources
+ List<String> resources = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry().getInterestedResources(user);
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ Stanza push = buildRosterPushStanza(userResource
+ .getFullQualifiedName(), sessionContext
+ .nextSequenceValue(), contact.getBareJID(), "to");
+ relayStanza(userResource, push, sessionContext);
+ }
+ } else {
+ // silently drop the stanza
+ }
+
+ return null;
+ }
+
+ @SpecCompliant(spec = "RFC3920bis-04", section = "3.1.3")
+ private Stanza handleInboundSubscriptionRequest(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ ResourceRegistry registry = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry();
+
+ Entity contact = stanza.getFrom();
+ Entity user = stanza.getTo();
+
+ // TODO: verify that user actually exists on this server
+ if (false) {
+ // user does not exist
+ return buildPresenceStanza(user.getBareJID(), contact.getBareJID(),
+ "unsubscribed");
+ }
+ // assert: user exists
+
+ // TODO: check whether user already has a subscription to
+ // contact
+ if (false) {
+ return buildPresenceStanza(user.getBareJID(), contact.getBareJID(),
+ "subscribed");
+ }
+
+ // user exists and doesn't have a subscription
+ // to the contact and user is currently online (# of
+ // interested resources > 0)
+ List<String> interestedResources = registry
+ .getInterestedResources(user);
+ if (interestedResources.isEmpty() == false) {
+ for (String resource : interestedResources) {
+ Entity userResource = new EntityImpl(user, resource);
+ relayStanza(userResource, stanza, sessionContext);
+ }
+ } else {
+
+ // contact exists, contact doesn't have a subscription
+ // to user and contact is currently offline
+ // TODO: store the subscription request in a msg queue for contact
+ // TODO: only store the subscription request once to prevent spam
+ }
+ return null;
+ }
+
+ @SpecCompliant(spec = "RFC3920bis-04", section = "3.1.2")
+ private void handleOutboundSubscriptionRequest(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ ServerRuntimeContext serverRuntimeContext = sessionContext
+ .getServerRuntimeContext();
+ StanzaRelay stanzaRelay = serverRuntimeContext.getStanzaRelay();
+
+ // relay the stanza to the contact's server
+ try {
+ stanzaRelay.relay(stanza.getTo(), stanza);
+ } catch (DeliveryException e) {
+ e.printStackTrace();
+ }
+
+ Entity user = stanza.getFrom();
+ Entity contact = stanza.getTo();
+
+ // send roster push to all of the user's interested resources
+ List<String> resources = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry().getInterestedResources(user);
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ Stanza push = buildRosterPushStanza(userResource
+ .getFullQualifiedName(),
+ sessionContext.nextSequenceValue(), contact.getBareJID(),
+ "none");
+ try {
+ stanzaRelay.relay(userResource, push);
+ } catch (DeliveryException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "4.2.2")
+ private void handleOutboundInitialPresence(XMPPCoreStanza stanza,
+ SessionContext sessionContext, Entity initiatingEntity) {
+ ResourceRegistry registry = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry();
+ List<String> resourceIDs = registry.getBoundResources(initiatingEntity);
+ Entity user;
+ if (resourceIDs.size() == 1) {
+ user = new EntityImpl(initiatingEntity, resourceIDs.get(0));
+ } else {
+ user = stanza.getFrom();
+ if (user == null) {
+ // return error stanza
+ }
+ }
+ registry.setResourceState(user.getResource(), ResourceState.AVAILABLE);
+ // 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: retrieve list of contacts that match the criteria mentioned
+ // above
+ List<Entity> contacts = new ArrayList<Entity>();
+ for (Entity contact : contacts) {
+ Stanza probeStanza = buildPresenceStanza(user
+ .getFullQualifiedName(), contact.getBareJID(), null);
+ relayStanza(contact, probeStanza, sessionContext);
+ }
+
+ // broadcast initial presence from full JID to contacts
+ // in roster with 'subscription' either 'from' or 'both'
+ // and not user is blocking outbound presence notifications
+ // TODO: retrieve list of contacts that match the criteria mentioned
+ // above
+ contacts = new ArrayList<Entity>();
+ for (Entity contact : contacts) {
+ Stanza presenceStanza = buildPresenceStanza(user
+ .getFullQualifiedName(), contact.getBareJID(), null);
+ relayStanza(contact, presenceStanza, sessionContext);
+ }
+
+ // TODO: also broadcast presence notification to all resources of
+ // current entity.
+ List<String> resources = registry.getAvailableResources(user);
+ for (String resource : resources) {
+ Entity otherResource = new EntityImpl(user, resource);
+ Stanza presenceStanza = buildPresenceStanza(user
+ .getFullQualifiedName(), otherResource
+ .getFullQualifiedName(), null);
+ relayStanza(otherResource, presenceStanza, sessionContext);
+ }
+
+ }
+
+ @SpecCompliant(spec = "RFC3921bis-04", section = "4.2.3")
+ private void handleInboundInitialPresence(XMPPCoreStanza stanza,
+ SessionContext sessionContext) {
+ Entity user = stanza.getTo();
+ List<String> resources = sessionContext.getServerRuntimeContext()
+ .getResourceRegistry().getAvailableResources(user);
+
+ for (String resource : resources) {
+ Entity userResource = new EntityImpl(user, resource);
+ relayStanza(userResource, stanza, sessionContext);
+ }
+ }
+
+ private XMPPCoreStanza buildPresenceStanza(String from, String to,
+ String type) {
+ StanzaBuilder builder = new StanzaBuilder("presence");
+ builder.addAttribute("from", from);
+ builder.addAttribute("to", to);
+ if (type != null) {
+ builder.addAttribute("type", type);
+ }
+ return XMPPCoreStanza.getWrapper(builder.getFinalStanza());
+ }
+
+ private Stanza buildRosterPushStanza(String to, String id,
+ String bareJidOfRosterItem, String subscription, String ask) {
+ StanzaBuilder builder = new StanzaBuilder("iq");
+ builder.addAttribute("to", to);
+ builder.addAttribute("type", "set");
+ builder.addAttribute("id", id);
+ builder.startInnerElement("query", NamespaceURIs.JABBER_IQ_ROSTER);
+ builder.startInnerElement("item");
+ builder.addAttribute("jid", bareJidOfRosterItem);
+ builder.addAttribute("subscription", subscription);
+ if (ask != null) {
+ builder.addAttribute("ask", ask);
+ }
+ builder.endInnerElement();
+ builder.endInnerElement();
+
+ return builder.getFinalStanza();
+ }
+
+ private Stanza buildRosterPushStanza(String to, String id,
+ String bareJidOfRosterItem, String subscription) {
+ return buildRosterPushStanza(to, id, bareJidOfRosterItem, subscription,
+ null);
+ }
+
+ private void relayStanza(Entity reviever, Stanza stanza,
+ SessionContext sessionContext) {
+ try {
+ sessionContext.getServerRuntimeContext().getStanzaRelay().relay(
+ reviever, stanza);
+ } catch (DeliveryException e) {
+ e.printStackTrace();
+ }
+ }
+
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@labs.apache.org
For additional commands, e-mail: commits-help@labs.apache.org