package org.buddycloud.channelserver.packetprocessor.iq.namespace.register;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import org.apache.log4j.Logger;
import org.buddycloud.channelserver.Configuration;
import org.buddycloud.channelserver.channel.ChannelManager;
import org.buddycloud.channelserver.channel.Conf;
import org.buddycloud.channelserver.db.NodeStore.Transaction;
import org.buddycloud.channelserver.db.exception.NodeStoreException;
import org.buddycloud.channelserver.packetprocessor.PacketProcessor;
import org.buddycloud.channelserver.packetprocessor.iq.namespace.pubsub.JabberPubsub;
import org.buddycloud.channelserver.pubsub.affiliation.Affiliations;
import org.buddycloud.channelserver.pubsub.event.Event;
import org.buddycloud.channelserver.pubsub.model.NodeItem;
import org.buddycloud.channelserver.pubsub.model.NodeMembership;
import org.buddycloud.channelserver.pubsub.model.NodeSubscription;
import org.buddycloud.channelserver.pubsub.subscription.Subscriptions;
import org.buddycloud.channelserver.utils.node.item.payload.Buddycloud;
import org.dom4j.Element;
import org.xmpp.packet.IQ;
import org.xmpp.packet.IQ.Type;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.PacketError.Condition;
import org.xmpp.resultsetmanagement.ResultSet;
public class UnregisterSet implements PacketProcessor<IQ> {
public static final String ELEMENT_NAME = "query";
private static final Logger LOGGER = Logger.getLogger(UnregisterSet.class);
private final BlockingQueue<Packet> outQueue;
private final ChannelManager channelManager;
public UnregisterSet(BlockingQueue<Packet> outQueue, ChannelManager channelManager) {
this.outQueue = outQueue;
this.channelManager = channelManager;
}
@Override
public void process(IQ request) throws Exception {
JID actorJID = null;
Element removeEl = request.getElement().element(
"query").element("remove");
Element actorEl = removeEl.element("actor");
boolean isRemote = actorEl != null;
if (isRemote) {
actorJID = new JID(actorEl.getTextTrim());
if (!checkDomain(request, actorJID)) {
return;
}
} else {
actorJID = new JID(request.getFrom().toBareJID());
}
unregister(request, actorJID, isRemote);
}
private boolean checkDomain(IQ request, JID actorJID) throws InterruptedException {
if (!request.getFrom().getDomain().contains(actorJID.getDomain())) {
fail(request, org.xmpp.packet.PacketError.Condition.bad_request,
org.xmpp.packet.PacketError.Type.cancel);
return false;
}
return true;
}
private void unregister(IQ request, JID actorJID, boolean isRemote) throws Exception {
LOGGER.debug("Processing unregister request from " + request.getFrom());
if (!validateSingleChildElement(request)) {
failBadRequest(request);
return;
}
if (!isRemote && !userRegistered(actorJID)) {
failRegistrationRequired(request);
return;
}
Transaction t = null;
try {
t = channelManager.beginTransaction();
List<Packet> notifications = new LinkedList<Packet>();
Set<String> remoteDomains = getRemoteDomains();
ResultSet<NodeMembership> userMemberships = channelManager.getUserMemberships(actorJID);
for (NodeMembership userMembership : userMemberships) {
String nodeId = userMembership.getNodeId();
if (isPersonal(nodeId) || isSingleOwner(nodeId, actorJID)) {
channelManager.deleteNode(nodeId);
if (Configuration.getInstance().isLocalNode(nodeId)) {
addDeleteNodeNotifications(nodeId, notifications);
}
}
if (!isRemote) {
addUnsubscribeFromNodeNotifications(actorJID,
userMembership.getNodeId(), notifications);
}
}
ResultSet<NodeItem> userItems = channelManager.getUserPublishedItems(actorJID);
for (NodeItem userItem : userItems) {
if (Configuration.getInstance().isLocalNode(userItem.getNodeId())) {
addDeleteItemNotifications(userItem.getNodeId(), userItem.getId(), notifications);
}
}
channelManager.deleteUserItems(actorJID);
channelManager.deleteUserSubscriptions(actorJID);
channelManager.deleteUserAffiliations(actorJID);
outQueue.put(IQ.createResultIQ(request));
if (!isRemote) {
makeRemoteRequests(request, remoteDomains);
}
sendNotifications(notifications);
t.commit();
} finally {
if (t != null) {
t.close();
}
}
}
private void sendNotifications(List<Packet> notifications) throws InterruptedException {
for (Packet notification : notifications) {
outQueue.put(notification);
}
}
private void makeRemoteRequests(IQ request, Set<String> remoteDomains) throws Exception {
for (String remoteDomain : remoteDomains) {
IQ remoteRequest = request.createCopy();
remoteRequest.getElement().addAttribute("remote-server-discover", "false");
remoteRequest.setTo(remoteDomain);
Element actor = remoteRequest.getElement()
.element("query").element("remove")
.addElement("actor", Buddycloud.NS);
actor.addText(request.getFrom().toBareJID());
outQueue.put(remoteRequest);
}
}
private Set<String> getRemoteDomains() throws NodeStoreException {
ArrayList<String> nodeList = channelManager.getNodeList();
Set<String> remoteDomains = new HashSet<String>();
for (String node : nodeList) {
try {
if (!Configuration.getInstance().isLocalNode(node)) {
remoteDomains.add(new JID(node.split("/")[2]).getDomain());
}
} catch (IllegalArgumentException e) {
// Ignore bad formatted nodes
}
}
return remoteDomains;
}
private void addUnsubscribeFromNodeNotifications(JID userJid,
String node, List<Packet> notifications) throws NodeStoreException {
ResultSet<NodeSubscription> subscribers = channelManager
.getNodeSubscriptionListeners(node);
Message messagePacket = createUnsubscribeNotification(userJid, node);
if (subscribers != null) {
for (NodeSubscription subscriber : subscribers) {
Message notification = messagePacket.createCopy();
notification.setTo(subscriber.getListener());
notifications.add(notification);
}
}
Collection<JID> adminUsers = Configuration.getInstance()
.getAdminUsers();
for (JID admin : adminUsers) {
Message notification = messagePacket.createCopy();
notification.setTo(admin);
notifications.add(notification);
}
}
private Message createUnsubscribeNotification(JID userJid, String node) {
Message messagePacket = new Message();
Element messageEl = messagePacket.getElement();
messageEl.addAttribute("remote-server-discover", "false");
Element event = messageEl.addElement("event", Event.NAMESPACE);
Element subscription = event.addElement("subscription");
messageEl.addAttribute("type", "headline");
subscription.addAttribute("subscription", "none");
subscription.addAttribute("jid", userJid.toBareJID());
subscription.addAttribute("node", node);
return messagePacket;
}
private void addDeleteItemNotifications(String node, String itemId,
List<Packet> notifications) throws NodeStoreException {
ResultSet<NodeSubscription> subscriptions = channelManager
.getNodeSubscriptionListeners(node);
Message notification = createItemDeleteNotificationMessage(node, itemId);
if (subscriptions != null) {
for (NodeSubscription subscription : subscriptions) {
if (subscription.getSubscription().equals(Subscriptions.subscribed)) {
notification.setTo(subscription.getListener().toString());
notifications.add(notification);
}
}
}
Collection<JID> adminUsers = Configuration.getInstance()
.getAdminUsers();
for (JID admin : adminUsers) {
notification.setTo(admin);
notifications.add(notification);
}
}
private Message createItemDeleteNotificationMessage(String node, String itemId) {
Message notification = new Message();
notification.setType(Message.Type.headline);
notification.getElement().addAttribute("remote-server-discover", "false");
Element event = notification.addChildElement("event",
JabberPubsub.NS_PUBSUB_EVENT);
Element items = event.addElement("items");
items.addAttribute("node", node);
Element retract = items.addElement("retract");
retract.addAttribute("id", itemId);
return notification;
}
private void addDeleteNodeNotifications(String node, List<Packet> notifications) throws Exception {
ResultSet<NodeSubscription> subscriptions = channelManager
.getNodeSubscriptionListeners(node);
Message notification = createNodeDeletedNotificationMessage(node);
if (subscriptions != null) {
for (NodeSubscription subscription : subscriptions) {
notification.setTo(subscription.getListener().toString());
notifications.add(notification.createCopy());
}
}
Collection<JID> adminUsers = Configuration.getInstance()
.getAdminUsers();
for (JID admin : adminUsers) {
notification.setTo(admin);
notifications.add(notification.createCopy());
}
}
private Message createNodeDeletedNotificationMessage(String node) {
Message notification = new Message();
notification.setType(Message.Type.headline);
notification.getElement().addAttribute("remote-server-discover", "false");
Element eventEl = notification.addChildElement("event",
JabberPubsub.NS_PUBSUB_EVENT);
Element deleteEl = eventEl.addElement("delete");
deleteEl.addAttribute("node", node);
return notification;
}
private boolean isSingleOwner(String nodeId, JID userJid) throws NodeStoreException {
ResultSet<NodeMembership> nodeMemberships = channelManager.getNodeMemberships(nodeId);
int ownerCount = 0;
boolean isOwner = false;
for (NodeMembership nodeMembership : nodeMemberships) {
if (nodeMembership.getAffiliation().equals(Affiliations.owner)) {
ownerCount++;
if (nodeMembership.getUser().equals(userJid)) {
isOwner = true;
}
}
}
return isOwner && ownerCount == 1;
}
private boolean isPersonal(String nodeId) throws NodeStoreException {
String channelType = channelManager.getNodeConfValue(nodeId, Conf.CHANNEL_TYPE);
return channelType != null && channelType.equals("personal");
}
private boolean validateSingleChildElement(IQ request) {
return request.getElement().element("query").elements().size() == 1;
}
private void failBadRequest(IQ request) throws InterruptedException {
fail(request,
org.xmpp.packet.PacketError.Condition.bad_request,
org.xmpp.packet.PacketError.Type.modify);
}
private void failRegistrationRequired(IQ request) throws InterruptedException {
fail(request,
org.xmpp.packet.PacketError.Condition.registration_required,
org.xmpp.packet.PacketError.Type.auth);
}
private void fail(IQ request, Condition condition, org.xmpp.packet.PacketError.Type type) throws InterruptedException {
IQ reply = IQ.createResultIQ(request);
reply.setType(Type.error);
PacketError pe = new PacketError(condition, type);
reply.setError(pe);
outQueue.put(reply);
}
private boolean userRegistered(JID actorJID) throws Exception {
return channelManager.nodeExists("/user/" + actorJID.toBareJID() + "/posts");
}
}