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