@Nullable
    private MUCRoom process(
        @Nonnull final Presence packet,
        @Nonnull final String roomName,
        @Nullable final MUCRoom room,
        @Nullable MUCOccupant preExistingOccupantData )
    {
        final Element mucInfo = packet.getChildElement("x", "http://jabber.org/protocol/muc"); // only sent in initial presence
        final String nickname = packet.getTo().getResource() == null
            || packet.getTo().getResource().trim().isEmpty() ? null
            : packet.getTo().getResource().trim();

        if ( preExistingOccupantData == null && Presence.Type.unavailable == packet.getType() ) {

            // This is for clustering scenarios where one node could already have cleaned up the clustered cache,
            // but the local node still needs to process the 'unavailable' presence of the leaving occupant.
            final MUCRoom localRoom = localMUCRoomManager.getLocalRooms().get(roomName);
            if (localRoom != null) {
                preExistingOccupantData = localRoom.getOccupantByFullJID(packet.getFrom());
            }

            if (preExistingOccupantData == null) {
                Log.debug("Silently ignoring user '{}' leaving a room that it has no role in '{}' (was the room just destroyed)?", packet.getFrom(), roomName);
                return null;
            } else {
                Log.debug("NOT silently ignoring user {} leaving a room. Sending 'unavailable' presence for room {} because the occupant was still present in the local room cache", packet.getFrom(), roomName);
            }
        }
        if ( preExistingOccupantData == null || (mucInfo != null && preExistingOccupantData.getNickname().equalsIgnoreCase(nickname) ) )
        {
            // If we're not already in a room (role == null), we either are joining it or it's not properly addressed and we drop it silently.
            // Alternative is that mucInfo is not null, in which case the client thinks it isn't in the room, so we should join anyway.
            return processRoomJoinRequest(packet, roomName, room, nickname);
        }
        else
        {
            // Check and reject conflicting packets with conflicting roles
            // In other words, another user already has this nickname
            if ( !preExistingOccupantData.getUserAddress().equals(packet.getFrom()) )
            {
                Log.debug("Rejecting conflicting stanza with conflicting roles: {}", packet.toXML());
                sendErrorPacket(packet, PacketError.Condition.conflict, "Another user uses this nickname.");
                return room;
            }

            if (room == null) {
                if (Presence.Type.unavailable == packet.getType()) {
                    Log.debug("Silently ignoring user '{}' leaving a non-existing room '{}' (was the room just destroyed)?", packet.getFrom(), roomName);
                } else {
                    Log.warn("Unable to process presence update from user '{}' to a non-existing room: {}", packet.getFrom(), roomName);
                }
                return null;
            }
            try
            {
                if ( nickname != null && !preExistingOccupantData.getNickname().equalsIgnoreCase(nickname) && Presence.Type.unavailable != packet.getType() )
                {
                    // Occupant has changed his nickname. Send two presences to each room occupant.
                    processNickNameChange(packet, room, preExistingOccupantData, nickname);
                }
                else
                {
                    processPresenceUpdate(packet, room, preExistingOccupantData);
                }
            }
            catch ( Exception e )
            {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
            return room;
        }
    }
Process a Presence stanza.

This method might be invoked for a room that does not yet exist (when the presence is a room-creation request).
This is why this method, unlike the process methods for Message and IQ stanza takes a <em>room name</em> argument
and returns the room that processed to request.

@param packet                  The stanza to process.
@param roomName                The name of the room that the stanza was addressed to.
@param room                    The room that the stanza was addressed to, if it exists.
@param preExistingOccupantData The occupant data of this user in the addressed room prior to processing of this stanza, if any.
@return the room that handled the request
xmppserver/src/main/java/org/jivesoftware/openfire/muc/spi/MultiUserChatServiceImpl.java