package org.buddycloud.channelserver.packetprocessor.iq.namespace.pubsub.get.items;
import java.io.StringReader;
import java.util.Map;
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.node.configuration.field.AccessModel;
import org.buddycloud.channelserver.db.CloseableIterator;
import org.buddycloud.channelserver.db.exception.NodeStoreException;
import org.buddycloud.channelserver.packetprocessor.iq.namespace.pubsub.JabberPubsub;
import org.buddycloud.channelserver.packetprocessor.iq.namespace.pubsub.PubSubElementProcessorAbstract;
import org.buddycloud.channelserver.pubsub.accessmodel.AccessModels;
import org.buddycloud.channelserver.pubsub.model.GlobalItemID;
import org.buddycloud.channelserver.pubsub.model.NodeItem;
import org.buddycloud.channelserver.pubsub.model.NodeMembership;
import org.buddycloud.channelserver.pubsub.model.impl.GlobalItemIDImpl;
import org.buddycloud.channelserver.utils.XMLConstants;
import org.buddycloud.channelserver.utils.node.NodeAclRefuseReason;
import org.buddycloud.channelserver.utils.node.item.payload.Buddycloud;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.dom.DOMElement;
import org.dom4j.io.SAXReader;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.PacketError.Condition;
import org.xmpp.packet.PacketError.Type;
public class NodeItemsGet extends PubSubElementProcessorAbstract {
private static final Logger LOGGER = Logger.getLogger(NodeItemsGet.class);
public static final int MAX_ITEMS_TO_RETURN = 50;
private ChannelManager channelManager;
private String node;
private String firstItem;
private String lastItem;
private SAXReader xmlReader;
private Element entry;
private IQ reply;
private Element resultSetManagement;
private Element element;
private boolean parentOnly = false;
private Map<String, String> nodeDetails;
private int rsmEntriesCount;
private JID actor;
private Element items;
public NodeItemsGet(BlockingQueue<Packet> outQueue, ChannelManager channelManager) {
setOutQueue(outQueue);
setChannelManager(channelManager);
this.acceptedElementName = XMLConstants.ITEMS_ELEM;
}
public void setChannelManager(ChannelManager ds) {
channelManager = ds;
}
@Override
public void process(Element elm, JID actorJID, IQ reqIQ, Element rsm) throws Exception {
request = reqIQ;
reply = IQ.createResultIQ(reqIQ);
element = elm;
resultSetManagement = rsm;
if (!Configuration.getInstance().isLocalJID(request.getFrom())) {
reply.getElement().addAttribute(XMLConstants.REMOTE_SERVER_DISCOVER_ATTR, Boolean.FALSE.toString());
}
try {
if (!isValidStanza()) {
outQueue.put(reply);
return;
}
boolean isCached = channelManager.isCachedNode(node);
this.actor = actorJID;
if (null == this.actor) {
this.actor = request.getFrom();
}
if (!Configuration.getInstance().isLocalNode(node) && !isCached) {
LOGGER.debug("Node " + node + " is remote and not cached, off to get some data");
makeRemoteRequest();
return;
}
if (!checkNodeExists()) {
setErrorCondition(PacketError.Type.cancel, PacketError.Condition.item_not_found);
outQueue.put(reply);
return;
}
if (!userCanViewNode()) {
outQueue.put(reply);
return;
}
xmlReader = new SAXReader();
if (null == items.element(XMLConstants.ITEM_ELEM)) {
getItems();
} else {
if (!getItem()) {
return;
}
}
} catch (NodeStoreException e) {
LOGGER.error(e);
setErrorCondition(PacketError.Type.wait, PacketError.Condition.internal_server_error);
} catch (NullPointerException e) {
LOGGER.error(e);
setErrorCondition(PacketError.Type.modify, PacketError.Condition.bad_request);
}
outQueue.put(reply);
}
protected boolean isValidStanza() {
Element pubsub = request.getElement().element(XMLConstants.PUBSUB_ELEM);
items = pubsub.element(XMLConstants.ITEMS_ELEM);
node = items.attributeValue(XMLConstants.NODE_ATTR);
return true;
}
private boolean getItem() throws Exception {
NodeItem nodeItem = channelManager.getNodeItem(node, items.element(XMLConstants.ITEM_ELEM).attributeValue(XMLConstants.ID_ATTR));
if (nodeItem == null) {
if (!Configuration.getInstance().isLocalNode(node)) {
makeRemoteRequest();
return false;
}
setErrorCondition(PacketError.Type.cancel, PacketError.Condition.item_not_found);
return true;
}
Element pubsub = reply.getElement().addElement(XMLConstants.PUBSUB_ELEM, JabberPubsub.NAMESPACE_URI);
Element items = pubsub.addElement(XMLConstants.ITEMS_ELEM).addAttribute(XMLConstants.NODE_ATTR, node);
addItemToResponse(nodeItem, items);
return true;
}
@Override
protected void makeRemoteRequest() throws InterruptedException {
request.setTo(new JID(node.split("/")[2]).getDomain());
if (null == request.getElement().element(XMLConstants.PUBSUB_ELEM).element(XMLConstants.ACTOR_ELEM)) {
Element actor = request.getElement().element(XMLConstants.PUBSUB_ELEM).addElement(XMLConstants.ACTOR_ELEM, Buddycloud.NS);
actor.addText(request.getFrom().toBareJID());
}
outQueue.put(request);
}
@Override
protected boolean checkNodeExists() throws NodeStoreException {
if (channelManager.nodeExists(node)) {
nodeDetails = channelManager.getNodeConf(node);
return true;
}
setErrorCondition(PacketError.Type.cancel, PacketError.Condition.item_not_found);
return false;
}
@Override
protected void setErrorCondition(Type type, Condition condition) {
reply.setType(IQ.Type.error);
PacketError error = new PacketError(condition, type);
reply.setError(error);
}
private void getItems() throws Exception {
Element pubsub = new DOMElement(XMLConstants.PUBSUB_ELEM, new org.dom4j.Namespace("", JabberPubsub.NAMESPACE_URI));
int maxItemsToReturn = MAX_ITEMS_TO_RETURN;
String afterItemId = null;
String parentOnlyAttribute = request.getChildElement().element(XMLConstants.ITEMS_ELEM).attributeValue(XMLConstants.PARENT_ONLY_ATTR);
if ((null != parentOnlyAttribute) && ((Boolean.TRUE.toString().equals(parentOnlyAttribute)) || ("1".equals(parentOnlyAttribute)))) {
parentOnly = true;
}
String maxItems = items.attributeValue(XMLConstants.MAX_ITEMS_ATTR);
if (maxItems != null) {
maxItemsToReturn = Integer.parseInt(maxItems);
}
if (resultSetManagement != null) {
Element max = resultSetManagement.element(XMLConstants.MAX_ELEM);
if (max != null) {
maxItemsToReturn = Integer.parseInt(max.getTextTrim());
}
Element after = resultSetManagement.element(XMLConstants.AFTER_ELEM);
if (after != null) {
try {
// Try and parse it as a global item id
GlobalItemID afterGlobalItemID = GlobalItemIDImpl.fromString(after.getTextTrim());
afterItemId = afterGlobalItemID.getItemID();
// Check it's for the correct node
if (!afterGlobalItemID.getNodeID().equals(node)) {
createExtendedErrorReply(Type.modify, Condition.item_not_found,
"RSM 'after' specifies an unexpected NodeID: " + afterGlobalItemID.getNodeID());
}
} catch (IllegalArgumentException e) {
// If the after isn't a valid 'tag:...' then it might just
// be a straight ItemID
afterItemId = after.getTextTrim();
LOGGER.error(e);
}
}
}
Element items = pubsub.addElement(XMLConstants.ITEMS_ELEM);
items.addAttribute(XMLConstants.NODE_ATTR, node);
entry = null;
int totalEntriesCount = getNodeItems(items, maxItemsToReturn, afterItemId, parentOnly);
if ((false == Configuration.getInstance().isLocalNode(node)) && (0 == rsmEntriesCount)) {
LOGGER.debug("No results in cache for remote node, so " + "we're going federated to get more");
makeRemoteRequest();
return;
}
if ((resultSetManagement != null) || (totalEntriesCount > maxItemsToReturn)) {
/*
* TODO(lloydwatkin), add result set here as defined in 6.5.4 Returning Some Items <set
* xmlns='http://jabber.org/protocol/rsm'> <first
* index='0'>368866411b877c30064a5f62b917cffe</first>
* <last>4e30f35051b7b8b42abe083742187228</last> <count>19</count> </set>
*/
Element rsm = pubsub.addElement(XMLConstants.SET_ELEM, "http://jabber.org/protocol/rsm");
if (firstItem != null) {
rsm.addElement(XMLConstants.FIRST_ELEM).setText(firstItem);
rsm.addElement(XMLConstants.LAST_ELEM).setText(lastItem);
}
rsm.addElement(XMLConstants.COUNT_ELEM).setText(Integer.toString(totalEntriesCount));
}
reply.setChildElement(pubsub);
}
@Override
protected boolean userCanViewNode() throws NodeStoreException {
NodeMembership nodeMembership = channelManager.getNodeMembership(node, actor);
if (getNodeViewAcl().canViewNode(node, nodeMembership, getNodeAccessModel(), Configuration.getInstance().isLocalJID(actor))) {
return true;
}
NodeAclRefuseReason reason = getNodeViewAcl().getReason();
createExtendedErrorReply(reason.getType(), reason.getCondition(), reason.getAdditionalErrorElement());
return false;
}
private AccessModels getNodeAccessModel() {
if (!nodeDetails.containsKey(AccessModel.FIELD_NAME)) {
return AccessModels.authorize;
}
return AccessModels.createFromString(nodeDetails.get(AccessModel.FIELD_NAME));
}
/**
* Get items nodes
*/
private int getNodeItems(Element items, int maxItemsToReturn, String afterItemId, boolean parentOnly) throws NodeStoreException {
CloseableIterator<NodeItem> itemIt = channelManager.getNodeItems(node, afterItemId, maxItemsToReturn, parentOnly);
rsmEntriesCount = 0;
if (itemIt == null) {
return 0;
}
try {
while (itemIt.hasNext()) {
++rsmEntriesCount;
NodeItem nodeItem = itemIt.next();
if (firstItem == null) {
firstItem = nodeItem.getId();
}
addItemToResponse(nodeItem, items);
lastItem = nodeItem.getId();
}
LOGGER.debug("Including RSM there are " + rsmEntriesCount + " items for node " + node);
return channelManager.countNodeItems(node, parentOnly);
} finally {
itemIt.close();
}
}
private void addItemToResponse(NodeItem nodeItem, Element parent) {
try {
entry = xmlReader.read(new StringReader(nodeItem.getPayload())).getRootElement();
Element item = parent.addElement(XMLConstants.ITEM_ELEM);
item.addAttribute("id", nodeItem.getId());
item.add(entry);
} catch (DocumentException e) {
LOGGER.error("Error parsing a node entry, ignoring. " + nodeItem);
}
}
protected void createExtendedErrorReply(Type type, Condition condition, String additionalElement) {
reply.setType(IQ.Type.error);
Element standardError = new DOMElement(condition.toString(), new org.dom4j.Namespace("", JabberPubsub.NS_XMPP_STANZAS));
Element extraError = new DOMElement(additionalElement, new org.dom4j.Namespace("", JabberPubsub.NS_PUBSUB_ERROR));
Element error = new DOMElement(XMLConstants.ERROR_ELEM);
error.addAttribute(XMLConstants.TYPE_ATTR, type.toString());
error.add(standardError);
error.add(extraError);
reply.setChildElement(error);
}
}