private IQ manageRoster(org.xmpp.packet.Roster packet) throws UnauthorizedException,
            UserAlreadyExistsException, SharedGroupException {

        IQ returnPacket = null;
        JID sender = packet.getFrom();
        IQ.Type type = packet.getType();

        try {
            if ((sender.getNode() == null || !RosterManager.isRosterServiceEnabled() ||
                    !userManager.isRegisteredUser(sender, false)) &&
                    IQ.Type.get == type) {
                // If anonymous user asks for his roster or roster service is disabled then
                // return an empty roster
                IQ reply = IQ.createResultIQ(packet);
                reply.setChildElement("query", "jabber:iq:roster");
                return reply;
            }
            if (!localServer.isLocal(sender)) {
                // Sender belongs to a remote server so discard this IQ request
                Log.warn("Discarding IQ roster packet of remote user: " + packet);
                return null;
            }

            Roster cachedRoster = userManager.getUser(sender.getNode()).getRoster();
            if (IQ.Type.get == type) {

                if (RosterManager.isRosterVersioningEnabled()) {
                    String clientVersion = packet.getChildElement().attributeValue("ver");
                    String latestVersion = String.valueOf( cachedRoster.getVersion() );
                    // Whether or not the roster has been modified since the version ID enumerated by the client, ...
                    if (!latestVersion.equals(clientVersion)) {
                        // ... the server MUST either return the complete roster
                        // (including a 'ver' attribute that signals the latest version)
                        returnPacket = cachedRoster.getReset();
                        returnPacket.getChildElement().addAttribute("ver", latestVersion );
                    } else {
                        // ... or return an empty IQ-result
                        returnPacket = new org.xmpp.packet.IQ();
                    }
                } else {
                    returnPacket = cachedRoster.getReset();
                }
                returnPacket.setType(IQ.Type.result);
                returnPacket.setTo(sender);
                returnPacket.setID(packet.getID());
                // Force delivery of the response because we need to trigger
                // a presence probe from all contacts
                deliverer.deliver(returnPacket);
                returnPacket = null;
            }
            else if (IQ.Type.set == type) {
                // RFC 6121 section 2.1.5 defines: The following rules apply to roster sets: [...] The server MUST
                // ignore any value of the 'subscription' attribute other than "remove"
                final org.xmpp.packet.Roster copy = ignoreNonRemoveSubscriptionValues(packet);
                returnPacket = IQ.createResultIQ(packet);

                // RFC 6121 2.3.3.  Error Cases:
                // The server MUST return a <bad-request/> stanza error to the client if the roster set contains any of the following violations:
                // The <query/> element contains more than one <item/> child element.
                if (copy.getItems().size() > 1) {
                    returnPacket.setError(new PacketError(PacketError.Condition.bad_request, PacketError.Type.modify, "Query contains more than one item"));
                } else {
                    for (org.xmpp.packet.Roster.Item item : copy.getItems()) {
                        if (item.getSubscription() == org.xmpp.packet.Roster.Subscription.remove) {
                            if (removeItem(cachedRoster, copy.getFrom(), item) == null) {
                                // RFC 6121 2.5.3.  Error Cases: If the value of the 'jid' attribute specifies an item that is not in the roster, then the server MUST return an <item-not-found/> stanza error.
                                returnPacket.setError(PacketError.Condition.item_not_found);
                            }
                        } else {
                            PacketError error = checkGroups(item.getGroups());
                            if (error != null) {
                                returnPacket.setError(error);
                            } else {
                                if (cachedRoster.isRosterItem(item.getJID())) {
                                    // existing item
                                    RosterItem cachedItem = cachedRoster.getRosterItem(item.getJID());
                                    cachedItem.setAsCopyOf(item);
                                    cachedRoster.updateRosterItem(cachedItem);
                                } else {
                                    // new item
                                    cachedRoster.createRosterItem(item);
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (UserNotFoundException e) {
            throw new UnauthorizedException(e);
        }

        return returnPacket;

    }
The packet is a typical 'set' or 'get' update targeted at the server.
Notice that the set could be a roster removal in which case we have to
generate a local roster removal update as well as a new roster removal
to send to the roster item's owner.

@param packet The packet that triggered this update
@return Either a response to the roster update or null if the packet is corrupt and the session was closed down
xmppserver/src/main/java/org/jivesoftware/openfire/handler/IQRosterHandler.java