/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ package org.apache.vysper.xmpp.modules.roster; import static org.apache.vysper.compliance.SpecCompliant.ComplianceCoverage.COMPLETE; import static org.apache.vysper.compliance.SpecCompliant.ComplianceCoverage.PARTIAL; import static org.apache.vysper.compliance.SpecCompliant.ComplianceStatus.FINISHED; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.vysper.compliance.SpecCompliance; import org.apache.vysper.compliance.SpecCompliant; import org.apache.vysper.xml.fragment.Attribute; import org.apache.vysper.xml.fragment.XMLElement; import org.apache.vysper.xml.fragment.XMLElementVerifier; import org.apache.vysper.xml.fragment.XMLSemanticError; import org.apache.vysper.xmpp.addressing.Entity; import org.apache.vysper.xmpp.addressing.EntityFormatException; import org.apache.vysper.xmpp.addressing.EntityImpl; import org.apache.vysper.xmpp.modules.roster.persistence.RosterManager; import org.apache.vysper.xmpp.stanza.IQStanza; /** * some roster logic * @author The Apache MINA Project (dev@mina.apache.org) */ public class RosterUtils { /** * takes the roster of a user and groups items by subscription state. this is helpful when all FROM items * are needed and then all TO items - but the roster is only iterated once. */ public static Map<SubscriptionType, List<RosterItem>> getRosterItemsByState(RosterManager rosterManager, Entity user) { Map<SubscriptionType, List<RosterItem>> rosterItemMap = new HashMap<SubscriptionType, List<RosterItem>>(); rosterItemMap.put(SubscriptionType.FROM, new ArrayList<RosterItem>()); rosterItemMap.put(SubscriptionType.TO, new ArrayList<RosterItem>()); rosterItemMap.put(SubscriptionType.BOTH, new ArrayList<RosterItem>()); rosterItemMap.put(SubscriptionType.REMOVE, new ArrayList<RosterItem>()); rosterItemMap.put(SubscriptionType.NONE, new ArrayList<RosterItem>()); Roster roster; try { roster = rosterManager.retrieve(user); } catch (RosterException e) { // TODO: make this errorhandling more intelligent throw new RuntimeException("could not retrieve roster for user " + user.getFullQualifiedName()); } // get items sorted by subscription type for (RosterItem rosterItem : roster) { rosterItemMap.get(rosterItem.getSubscriptionType()).add(rosterItem); } return rosterItemMap; } /** * extracts a roster item from the given stanza */ public static RosterItem parseRosterItem(IQStanza stanza) throws RosterBadRequestException, RosterNotAcceptableException { return parseRosterItem(stanza, false); // do not read subscription types (except 'remove') } /** * extracts a roster item from the given stanza, with relaxed semantical checks for testing */ public static RosterItem parseRosterItemForTesting(IQStanza stanza) throws RosterBadRequestException, RosterNotAcceptableException { return parseRosterItem(stanza, true); // do also parse subscription types } /** * extracts a roster item from the stanza and checks for integrity according to the XMPP spec */ @SpecCompliance(compliant = { @SpecCompliant(spec = "rfc3921bis-08", section = "2.1.1", status = FINISHED, coverage = COMPLETE), @SpecCompliant(spec = "rfc3921bis-08", section = "2.1.3", status = FINISHED, coverage = PARTIAL, comment = "handles the conformance rules 1-3 when parseSubscriptionTypes is set to false") }) private static RosterItem parseRosterItem(IQStanza stanza, boolean parseSubscriptionTypes) throws RosterBadRequestException, RosterNotAcceptableException { XMLElement queryElement; try { queryElement = stanza.getSingleInnerElementsNamed("query"); if (queryElement == null) throw new XMLSemanticError("missing query node"); } catch (XMLSemanticError xmlSemanticError) { throw new RosterBadRequestException("roster set needs a single query node."); } XMLElement itemElement; try { itemElement = queryElement.getSingleInnerElementsNamed("item"); if (itemElement == null) throw new XMLSemanticError("missing item node"); } catch (XMLSemanticError xmlSemanticError) { throw new RosterBadRequestException("roster set needs a single item node."); } Attribute attributeJID = itemElement.getAttribute("jid"); if (attributeJID == null || attributeJID.getValue() == null) throw new RosterBadRequestException("missing 'jid' attribute on item node"); XMLElementVerifier verifier = itemElement.getVerifier(); String name = verifier.attributePresent("name") ? itemElement.getAttribute("name").getValue() : null; if (name != null && name.length() > RosterConfiguration.ROSTER_ITEM_NAME_MAX_LENGTH) { throw new RosterNotAcceptableException("roster name too long: " + name.length()); } SubscriptionType subscription = verifier.attributePresent("subscription") ? SubscriptionType .valueOf(itemElement.getAttribute("subscription").getValue().toUpperCase()) : SubscriptionType.NONE; if (!parseSubscriptionTypes && subscription != SubscriptionType.REMOVE) subscription = SubscriptionType.NONE; // roster remove is always tolerated AskSubscriptionType askSubscriptionType = AskSubscriptionType.NOT_SET; if (parseSubscriptionTypes) { askSubscriptionType = verifier.attributePresent("ask") ? AskSubscriptionType.valueOf("ASK_" + itemElement.getAttribute("ask").getValue().toUpperCase()) : AskSubscriptionType.NOT_SET; } String contactJid = attributeJID.getValue(); Entity contact; try { contact = EntityImpl.parse(contactJid); } catch (EntityFormatException e) { throw new RosterNotAcceptableException("jid cannot be parsed: " + contactJid); } List<RosterGroup> groups = new ArrayList<RosterGroup>(); List<XMLElement> groupElements = itemElement.getInnerElementsNamed("group"); if (groupElements != null) { for (XMLElement groupElement : groupElements) { String groupName = null; try { groupName = groupElement.getSingleInnerText().getText(); } catch (XMLSemanticError xmlSemanticError) { throw new RosterBadRequestException("roster item group node is malformed"); } if (StringUtils.isEmpty(groupName)) { throw new RosterNotAcceptableException("roster item group name of zero length"); } else if (groupName.length() > RosterConfiguration.ROSTER_GROUP_NAME_MAX_LENGTH) { throw new RosterNotAcceptableException("roster item group name too long: " + groupName.length()); } RosterGroup group = new RosterGroup(groupName); if (groups.contains(group) && !RosterConfiguration.ROSTER_ITEM_GROUP_ALLOW_DUPLICATES) { throw new RosterNotAcceptableException("duplicate roster group name: " + groupName); } else { groups.add(group); } } } RosterItem rosterItem = new RosterItem(contact, name, subscription, askSubscriptionType, groups); return rosterItem; } }