tigase.muc.modules.PresenceModule.java Source code

Java tutorial

Introduction

Here is the source code for tigase.muc.modules.PresenceModule.java

Source

/*
 * PresenceModule.java
 *
 * Tigase Jabber/XMPP Server
 * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. Look for COPYING file in the top folder.
 * If not, see http://www.gnu.org/licenses/.
 *
 */

package tigase.muc.modules;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import com.github.sociallabel.tigase.integrate.DataUtil;

import tigase.component.ElementWriter;
import tigase.component.exceptions.RepositoryException;
import tigase.criteria.Criteria;
import tigase.criteria.ElementCriteria;
import tigase.muc.Affiliation;
import tigase.muc.DateUtil;
import tigase.muc.Ghostbuster2;
import tigase.muc.MucConfig;
import tigase.muc.Role;
import tigase.muc.Room;
import tigase.muc.RoomConfig;
import tigase.muc.RoomConfig.Anonymity;
import tigase.muc.exceptions.MUCException;
import tigase.muc.history.HistoryProvider;
import tigase.muc.logger.MucLogger;
import tigase.muc.modules.PresenceModule.DelayDeliveryThread.DelDeliverySend;
import tigase.muc.repository.IMucRepository;
import tigase.server.Message;
import tigase.server.Packet;
import tigase.server.Priority;
import tigase.util.TigaseStringprepException;
import tigase.xml.Element;
import tigase.xml.XMLNodeIfc;
import tigase.xmpp.Authorization;
import tigase.xmpp.BareJID;
import tigase.xmpp.JID;

/**
 * @author bmalkow
 * 
 */
public class PresenceModule extends AbstractModule {
    /**
     * Class description
     * 
     * 
     * @version Enter version here..., 13/02/20
     * @author Enter your name here...
     */
    public static class DelayDeliveryThread extends Thread {
        /**
         * Interface description
         * 
         * 
         * @version Enter version here..., 13/02/20
         * @author Enter your name here...
         */
        public static interface DelDeliverySend {
            /**
             * Method description
             * 
             * 
             * @param packet
             */
            void sendDelayedPacket(Packet packet);
        }

        private final LinkedList<Element[]> items = new LinkedList<Element[]>();

        private final DelDeliverySend sender;

        /**
         * Constructs ...
         * 
         * 
         * @param component
         */
        public DelayDeliveryThread(DelDeliverySend component) {
            this.sender = component;
        }

        /**
         * @param elements
         */
        public void put(Collection<Element> elements) {
            if ((elements != null) && (elements.size() > 0)) {
                items.push(elements.toArray(new Element[] {}));
            }
        }

        /**
         * Method description
         * 
         * 
         * @param element
         */
        public void put(Element element) {
            items.add(new Element[] { element });
        }

        /**
         * Method description
         * 
         */
        @Override
        public void run() {
            try {
                do {
                    sleep(553);
                    if (items.size() > 0) {
                        Element[] toSend = items.poll();

                        if (toSend != null) {
                            for (Element element : toSend) {
                                try {
                                    sender.sendDelayedPacket(Packet.packetInstance(element));
                                } catch (TigaseStringprepException ex) {
                                    if (log.isLoggable(Level.INFO)) {
                                        log.info("Packet addressing problem, stringprep failed: " + element);
                                    }
                                }
                            }
                        }
                    }
                } while (true);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Class description
     * 
     * 
     * @version Enter version here..., 13/02/20
     * @author Enter your name here...
     */
    public static class PresenceWrapper {
        final Element[] items;
        final Packet packet;
        final Element x;

        PresenceWrapper(Packet packet, Element x, Element[] items) {
            this.packet = packet;
            this.x = x;
            this.items = items;
        }

        /**
         * Method description
         * 
         * 
         * @param code
         */
        void addStatusCode(int code) {
            x.addChild(new Element("status", new String[] { "code" }, new String[] { "" + code }));
        }
    }

    private static final Criteria CRIT = ElementCriteria.name("presence");

    /** Field description */
    protected static final Logger log = Logger.getLogger(PresenceModule.class.getName());

    protected static void addCodes(PresenceWrapper wrapper, boolean newRoomCreated, String newNickName) {
        if (newRoomCreated) {
            wrapper.addStatusCode(201);
        }
        if (newNickName != null) {
            wrapper.addStatusCode(303);

            for (Element item : wrapper.items) {
                item.setAttribute("nick", newNickName);
            }
        }
    }

    private static Role getDefaultRole(final RoomConfig config, final Affiliation affiliation) {
        Role newRole;

        if (config.isRoomModerated() && (affiliation == Affiliation.none)) {
            newRole = Role.visitor;
        } else {
            switch (affiliation) {
            case admin:
                newRole = Role.moderator;

                break;

            case member:
                newRole = Role.participant;

                break;

            case none:
                newRole = Role.participant;

                break;

            case outcast:
                newRole = Role.none;

                break;

            case owner:
                newRole = Role.moderator;

                break;

            default:
                newRole = Role.none;

                break;
            }
        }

        return newRole;
    }

    /**
     * Method description
     * 
     * 
     * @param room
     * @param destinationJID
     * @param presence
     * @param occupantBareJID
     * @param occupantNickname
     * @param occupantAffiliation
     * @param occupantRole
     * 
     * @return
     * 
     * @throws TigaseStringprepException
     */
    static PresenceWrapper preparePresenceW(Room room, JID destinationJID, final Element presence,
            BareJID occupantBareJID, Collection<JID> occupantJIDs, String occupantNickname,
            Affiliation occupantAffiliation, Role occupantRole) throws TigaseStringprepException {
        Anonymity anonymity = room.getConfig().getRoomAnonymity();
        final Affiliation destinationAffiliation = room.getAffiliation(destinationJID.getBareJID());

        try {
            presence.setAttribute("from", JID.jidInstance(room.getRoomJID(), occupantNickname).toString());
        } catch (TigaseStringprepException e) {
            presence.setAttribute("from", room.getRoomJID() + "/" + occupantNickname);
        }
        presence.setAttribute("to", destinationJID.toString());

        Element x = new Element("x", new String[] { "xmlns" },
                new String[] { "http://jabber.org/protocol/muc#user" });

        final ArrayList<Element> items = new ArrayList<Element>();
        for (JID jid : occupantJIDs) {
            Element item = new Element("item", new String[] { "affiliation", "role", "nick" },
                    new String[] { occupantAffiliation.name(), occupantRole.name(), occupantNickname });
            x.addChild(item);
            items.add(item);

            if ((anonymity == Anonymity.nonanonymous)
                    || ((anonymity == Anonymity.semianonymous) && destinationAffiliation.isViewOccupantsJid())) {
                item.setAttribute("jid", jid.toString());
            } else
                break;

        }

        presence.addChild(x);

        Packet packet = Packet.packetInstance(presence);
        PresenceWrapper wrapper = new PresenceWrapper(packet, x, items.toArray(new Element[] {}));

        if (occupantBareJID != null && occupantBareJID.equals(destinationJID.getBareJID())) {
            wrapper.packet.setPriority(Priority.HIGH);
            wrapper.addStatusCode(110);
            if (anonymity == Anonymity.nonanonymous) {
                wrapper.addStatusCode(100);
            }
            if (room.getConfig().isLoggingEnabled()) {
                wrapper.addStatusCode(170);
            }
        }

        return wrapper;
    }

    static PresenceWrapper preparePresenceW(Room room, JID destinationJID, final Element presence,
            BareJID occupantJID, String occupantNickname, Affiliation occupantAffiliation, Role occupantRole)
            throws TigaseStringprepException {
        final Collection<JID> occupantJIDs = room.getOccupantsJidsByNickname(occupantNickname);
        return preparePresenceW(room, destinationJID, presence, occupantJID, occupantJIDs, occupantNickname,
                occupantAffiliation, occupantRole);
    }

    /**
     * Method description
     * 
     * 
     * @param room
     * @param destinationJID
     * @param presence
     * @param occupantJID
     * 
     * @return
     * 
     * @throws TigaseStringprepException
     */
    static PresenceWrapper preparePresenceW(Room room, JID destinationJID, final Element presence, JID occupantJID)
            throws TigaseStringprepException {
        final String occupantNickname = room.getOccupantsNickname(occupantJID);

        return preparePresenceW(room, destinationJID, presence, occupantNickname);
    }

    /**
     * Method description
     * 
     * 
     * @param room
     * @param destinationJID
     * @param presence
     * @param occupantNickname
     * 
     * @return
     * 
     * @throws TigaseStringprepException
     */
    static PresenceWrapper preparePresenceW(Room room, JID destinationJID, final Element presence,
            String occupantNickname) throws TigaseStringprepException {
        final BareJID occupantJID = room.getOccupantsJidByNickname(occupantNickname);
        final Affiliation occupantAffiliation = room.getAffiliation(occupantJID);
        final Role occupantRole = room.getRole(occupantNickname);

        return preparePresenceW(room, destinationJID, presence, occupantJID, occupantNickname, occupantAffiliation,
                occupantRole);
    }

    private static Integer toInteger(String v, Integer defaultValue) {
        if (v == null) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(v);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    private final Set<Criteria> allowedElements = new HashSet<Criteria>();

    private boolean filterEnabled = true;

    private Ghostbuster2 ghostbuster;

    private final HistoryProvider historyProvider;

    private boolean lockNewRoom = true;

    private final MucLogger mucLogger;

    /**
     * Constructs ...
     * 
     * 
     * @param config
     * @param writer
     * @param mucRepository
     * @param historyProvider
     * @param sender
     * @param mucLogger
     */
    public PresenceModule(MucConfig config, ElementWriter writer, IMucRepository mucRepository,
            HistoryProvider historyProvider, DelDeliverySend sender, MucLogger mucLogger,
            Ghostbuster2 ghostbuster) {
        super(config, writer, mucRepository);
        this.historyProvider = historyProvider;
        this.mucLogger = mucLogger;
        this.filterEnabled = config.isPresenceFilterEnabled();
        this.ghostbuster = ghostbuster;
        allowedElements.add(ElementCriteria.name("show"));
        allowedElements.add(ElementCriteria.name("status"));
        allowedElements.add(ElementCriteria.name("priority"));
        allowedElements.add(ElementCriteria.xmlns("http://jabber.org/protocol/caps"));
        if (log.isLoggable(Level.CONFIG)) {
            log.config("Filtering presence children is " + (filterEnabled ? "enabled" : "disabled"));
        }
    }

    /**
     * @param room
     * @param date
     * @param senderJID
     * @param nickName
     */
    private void addJoinToHistory(Room room, Date date, JID senderJID, String nickName) {
        if (historyProvider != null) {
            historyProvider.addJoinEvent(room, date, senderJID, nickName);
        }
        if ((mucLogger != null) && room.getConfig().isLoggingEnabled()) {
            mucLogger.addJoinEvent(room, date, senderJID, nickName);
        }
    }

    /**
     * @param room
     * @param date
     * @param senderJID
     * @param nickName
     */
    private void addLeaveToHistory(Room room, Date date, JID senderJID, String nickName) {
        if (historyProvider != null) {
            historyProvider.addLeaveEvent(room, date, senderJID, nickName);
        }
        if ((mucLogger != null) && room.getConfig().isLoggingEnabled()) {
            mucLogger.addLeaveEvent(room, date, senderJID, nickName);
        }
    }

    /**
     * Method description
     * 
     * 
     * @param element
     * 
     * @return
     */
    protected Element clonePresence(Element element) {
        Element presence = new Element(element);

        if (filterEnabled) {
            List<Element> cc = element.getChildren();

            if (cc != null) {
                @SuppressWarnings("rawtypes")
                List<XMLNodeIfc> children = new ArrayList<XMLNodeIfc>();

                for (Element c : cc) {
                    for (Criteria crit : allowedElements) {
                        if (crit.match(c)) {
                            children.add(c);

                            break;
                        }
                    }
                }
                presence.setChildren(children);
            }
        }

        Element toRemove = presence.getChild("x", "http://jabber.org/protocol/muc");

        if (toRemove != null) {
            presence.removeChild(toRemove);
        }

        return presence;
    }

    /**
     * @param room
     * @param senderJID
     * @throws TigaseStringprepException
     */
    public void doQuit(final Room room, final JID senderJID) throws TigaseStringprepException {
        final String leavingNickname = room.getOccupantsNickname(senderJID);
        final Affiliation leavingAffiliation = room.getAffiliation(leavingNickname);
        final Role leavingRole = room.getRole(leavingNickname);
        Element presenceElement = new Element("presence");

        presenceElement.setAttribute("type", "unavailable");

        Collection<JID> occupantJIDs = new ArrayList<JID>(room.getOccupantsJidsByNickname(leavingNickname));
        boolean nicknameGone = room.removeOccupant(senderJID);
        ghostbuster.remove(senderJID, room);

        room.updatePresenceByJid(senderJID, null);

        if (config.isMultiItemMode()) {
            final PresenceWrapper selfPresence = preparePresenceW(room, senderJID, presenceElement,
                    senderJID.getBareJID(), occupantJIDs, leavingNickname, leavingAffiliation, leavingRole);
            writer.write(selfPresence.packet);
        } else {
            Collection<JID> z = new ArrayList<JID>(1);
            z.add(senderJID);

            final PresenceWrapper selfPresence = preparePresenceW(room, senderJID, presenceElement,
                    senderJID.getBareJID(), z, leavingNickname, leavingAffiliation, leavingRole);
            writer.write(selfPresence.packet);
        }

        // TODO if highest priority is gone, then send current highest priority
        // to occupants
        if (nicknameGone) {
            for (String occupantNickname : room.getOccupantsNicknames()) {
                for (JID occupantJid : room.getOccupantsJidsByNickname(occupantNickname)) {
                    presenceElement = new Element("presence");
                    presenceElement.setAttribute("type", "unavailable");

                    PresenceWrapper presence = preparePresenceW(room, occupantJid, presenceElement,
                            senderJID.getBareJID(), occupantJIDs, leavingNickname, leavingAffiliation, leavingRole);

                    writer.write(presence.packet);
                }
            }
            if (room.getConfig().isLoggingEnabled()) {
                addLeaveToHistory(room, new Date(), senderJID, leavingNickname);
            }
        } else {
            occupantJIDs = new ArrayList<JID>(room.getOccupantsJidsByNickname(leavingNickname));

            Element pe = room.getLastPresenceCopyByJid(senderJID.getBareJID());
            for (String occupantNickname : room.getOccupantsNicknames()) {
                for (JID occupantJid : room.getOccupantsJidsByNickname(occupantNickname)) {
                    if (config.isMultiItemMode()) {
                        PresenceWrapper presence = preparePresenceW(room, occupantJid, pe.clone(),
                                senderJID.getBareJID(), occupantJIDs, leavingNickname, leavingAffiliation,
                                leavingRole);
                        writer.write(presence.packet);
                    } else {
                        for (JID jid : occupantJIDs) {
                            Collection<JID> z = new ArrayList<JID>(1);
                            z.add(jid);
                            PresenceWrapper presence = preparePresenceW(room, occupantJid, pe.clone(),
                                    senderJID.getBareJID(), z, leavingNickname, leavingAffiliation, leavingRole);
                            writer.write(presence.packet);
                        }
                    }
                }
            }

        }
        if (room.getOccupantsCount() == 0) {
            if ((historyProvider != null) && !room.getConfig().isPersistentRoom()) {
                this.historyProvider.removeHistory(room);
            }
            this.repository.leaveRoom(room);
        }
    }

    /**
     * Method description
     * 
     * 
     * @return
     */
    @Override
    public String[] getFeatures() {
        return null;
    }

    /**
     * Method description
     * 
     * 
     * @return
     */
    @Override
    public Criteria getModuleCriteria() {
        return CRIT;
    }

    /**
     * Method description
     * 
     * 
     * @return
     */
    public boolean isLockNewRoom() {
        return lockNewRoom;
    }

    private PresenceWrapper preparePresence(JID destinationJID, final Element presence, Room room, JID occupantJID,
            boolean newRoomCreated, String newNickName) throws TigaseStringprepException {
        final PresenceWrapper wrapper = preparePresenceW(room, destinationJID, presence, occupantJID);

        addCodes(wrapper, newRoomCreated, newNickName);

        return wrapper;
    }

    @Override
    protected void sendMucMessage(Room room, String recipientNickame, String message)
            throws TigaseStringprepException {
        super.sendMucMessage(room, recipientNickame, message);
    }

    /**
    * Method description
    * 
    * 
    * @param element
    * 
    * @throws MUCException
    * @throws TigaseStringprepException
    */
    @Override
    public void process(Packet element) throws MUCException, TigaseStringprepException {
        final JID senderJID = JID.jidInstance(element.getAttributeStaticStr(Packet.FROM_ATT));
        final BareJID roomJID = BareJID.bareJIDInstance(element.getAttributeStaticStr(Packet.TO_ATT));
        final String nickName = getNicknameFromJid(JID.jidInstance(element.getAttributeStaticStr(Packet.TO_ATT)));
        final String presenceType = element.getAttributeStaticStr(Packet.TYPE_ATT);

        // final String id = element.getAttribute("id");
        if ((presenceType != null) && "error".equals(presenceType)) {
            if (log.isLoggable(Level.FINER)) {
                log.finer("Ignoring presence with type='" + presenceType + "' from " + senderJID);
            }

            return;
        }
        if (nickName == null) {
            throw new MUCException(Authorization.JID_MALFORMED);
        }
        try {
            Room room = repository.getRoom(roomJID);

            if ((presenceType != null) && "unavailable".equals(presenceType)) {
                processExit(room, element.getElement(), senderJID);

                return;
            }

            final String knownNickname;
            final boolean roomCreated;

            int roomStauts = internalValidRoom(roomJID.toString(), senderJID.toString());

            if ((roomStauts == -1) || (roomStauts != 0 && room == null)) {
                throw new MUCException(Authorization.NOT_ALLOWED, "you are not allowed to enter this room");
            }

            if (room == null) {
                if (log.isLoggable(Level.INFO)) {
                    log.info("Creating new room '" + roomJID + "' by user " + nickName + "' <"
                            + senderJID.toString() + ">");
                }
                room = repository.createNewRoom(roomJID, senderJID);
                room.addAffiliationByJid(senderJID.getBareJID(), Affiliation.owner);
                room.setRoomLocked(this.lockNewRoom);
                room.setRoomLocked(false);
                roomCreated = true;
                knownNickname = null;
                room.getConfig().notifyConfigUpdate();
            } else {
                roomCreated = false;
                knownNickname = room.getOccupantsNickname(senderJID);
            }

            final boolean probablyReEnter = element.getElement().getChild("x",
                    "http://jabber.org/protocol/muc") != null;

            if ((knownNickname != null) && !knownNickname.equals(nickName)) {
                processChangeNickname(room, element.getElement(), senderJID, knownNickname, nickName);
            } else if (probablyReEnter || (knownNickname == null)) {
                processEntering(room, roomCreated, element.getElement(), senderJID, nickName);
            } else if (knownNickname.equals(nickName)) {
                processChangeAvailabilityStatus(room, element.getElement(), senderJID, knownNickname);
            }
        } catch (MUCException e) {
            throw e;
        } catch (TigaseStringprepException e) {
            throw e;
        } catch (RepositoryException e) {
            throw new RuntimeException(e);
        }
    }

    private int internalValidRoom(String room, String user) {
        int result = -1;
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        try {
            String userId = user.substring(0, user.indexOf("@"));
            String domain = user.substring(user.indexOf("@"), user.length());
            String roomId = room.substring(0, room.indexOf("@"));
            map.add("roomId", roomId);
            ResponseEntity<Map> data = DataUtil.postForm(DataUtil.WEB_SERVER_ADDRESS + "internalRoom", map,
                    Map.class);
            Map body = data.getBody();
            if (data.getStatusCode() == HttpStatus.OK) {
                String ownerId = (String) body.get("ownerId");
                String status = (String) body.get("status");
                if (ownerId != null && ownerId.equals(userId)) {
                    result = 0;
                } else if ("1".equals(status)) {
                    result = 1;
                }
            }
        } catch (Exception e) {
            // TODO log exception
            e.printStackTrace();
        }
        return result;
    }

    private void internalCountRoom(String room) {
        try {
            String roomId = room.substring(0, room.indexOf("@"));
            ResponseEntity<Map> data = DataUtil.postJson(DataUtil.WEB_SERVER_ADDRESS + "internalCountRoom/{roomId}",
                    null, Map.class, roomId);
            Map body = data.getBody();
            if (data.getStatusCode() == HttpStatus.OK) {
                //ok
            }
        } catch (Exception e) {
            // TODO log exception
            e.printStackTrace();
        }
    }

    /**
     * Method description
     * 
     * 
     * @param room
     * @param presenceElement
     * @param senderJID
     * @param nickname
     * 
     * @throws TigaseStringprepException
     */
    protected void processChangeAvailabilityStatus(final Room room, final Element presenceElement,
            final JID senderJID, final String nickname) throws TigaseStringprepException {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Processing stanza " + presenceElement.toString());
        }
        room.updatePresenceByJid(null, clonePresence(presenceElement));

        Element pe = room.getLastPresenceCopyByJid(senderJID.getBareJID());

        sendPresenceToAllOccupants(pe, room, senderJID, false, null);
    }

    /**
     * Method description
     * 
     * 
     * @param room
     * @param element
     * @param senderJID
     * @param senderNickname
     * @param newNickName
     * 
     * @throws MUCException
     * @throws TigaseStringprepException
     */
    protected void processChangeNickname(final Room room, final Element element, final JID senderJID,
            final String senderNickname, final String newNickName) throws TigaseStringprepException, MUCException {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Processing stanza " + element.toString());
        }

        throw new MUCException(Authorization.FEATURE_NOT_IMPLEMENTED, "Will me done soon");

        // TODO Example 23. Service Denies Room Join Because Roomnicks Are
        // Locked Down (???)
    }

    /**
     * Method description
     * 
     * 
     * @param room
     * @param roomCreated
     * @param element
     * @param senderJID
     * @param nickname
     * 
     * @throws MUCException
     * @throws TigaseStringprepException
     */
    protected void processEntering(final Room room, final boolean roomCreated, final Element element,
            final JID senderJID, final String nickname) throws MUCException, TigaseStringprepException {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Processing stanza " + element.toString());
        }

        final Affiliation affiliation = room.getAffiliation(senderJID.getBareJID());
        final Element xElement = element.getChild("x", "http://jabber.org/protocol/muc");
        final Element password = (xElement == null) ? null : xElement.getChild("password");

        if (room.getConfig().isPasswordProtectedRoom()) {
            final String psw = (password == null) ? null : password.getCData();
            final String roomPassword = room.getConfig().getPassword();

            if ((psw == null) || !psw.equals(roomPassword)) {

                // Service Denies Access Because No Password Provided
                if (log.isLoggable(Level.FINEST)) {
                    log.finest("Password '" + psw + "' is not match to room password '" + roomPassword + "' ");
                }

                throw new MUCException(Authorization.NOT_AUTHORIZED);
            }
        }
        if (room.isRoomLocked() && (affiliation != Affiliation.owner)) {

            // Service Denies Access Because Room Does Not (Yet) Exist
            throw new MUCException(Authorization.ITEM_NOT_FOUND, null, "Room exists but is locked");
        }
        if (!affiliation.isEnterOpenRoom()) {

            // Service Denies Access Because User is Banned
            if (log.isLoggable(Level.INFO)) {
                log.info("User " + nickname + "' <" + senderJID.toString() + "> is on rooms '" + room.getRoomJID()
                        + "' blacklist");
            }

            throw new MUCException(Authorization.FORBIDDEN);
        } else if (room.getConfig().isRoomMembersOnly() && !affiliation.isEnterMembersOnlyRoom()) {

            // Service Denies Access Because User Is Not on Member List
            if (log.isLoggable(Level.INFO)) {
                log.info("User " + nickname + "' <" + senderJID.toString() + "> is NOT on rooms '"
                        + room.getRoomJID() + "' member list.");
            }

            throw new MUCException(Authorization.REGISTRATION_REQUIRED);
        }

        final BareJID currentOccupantJid = room.getOccupantsJidByNickname(nickname);

        if ((currentOccupantJid != null) && !currentOccupantJid.equals(senderJID.getBareJID())) {

            // Service Denies Access Because of Nick Conflict
            throw new MUCException(Authorization.CONFLICT);
        }

        // TODO Service Informs User that Room Occupant Limit Has Been Reached
        // Service Sends Presence from Existing Occupants to New Occupant
        for (String occupantNickname : room.getOccupantsNicknames()) {
            final BareJID occupantJid = room.getOccupantsJidByNickname(occupantNickname);
            if (currentOccupantJid != null && currentOccupantJid.equals(occupantJid)) {
                continue;
            }
            Element op = room.getLastPresenceCopyByJid(occupantJid);

            final Collection<JID> occupantJIDs = room.getOccupantsJidsByNickname(occupantNickname);
            final BareJID occupantBareJID = room.getOccupantsJidByNickname(occupantNickname);
            final Affiliation occupantAffiliation = room.getAffiliation(occupantBareJID);
            final Role occupantRole = room.getRole(occupantNickname);

            if (config.isMultiItemMode()) {
                PresenceWrapper l = preparePresenceW(room, senderJID, op.clone(), occupantBareJID, occupantJIDs,
                        occupantNickname, occupantAffiliation, occupantRole);
                writer.write(l.packet);
            } else {
                for (JID jid : occupantJIDs) {
                    Collection<JID> z = new ArrayList<JID>(1);
                    z.add(jid);
                    PresenceWrapper l = preparePresenceW(room, senderJID, op.clone(), occupantBareJID, z,
                            occupantNickname, occupantAffiliation, occupantRole);
                    writer.write(l.packet);
                }
            }
        }

        final Role newRole = getDefaultRole(room.getConfig(), affiliation);

        if (log.isLoggable(Level.FINEST)) {
            log.finest("Occupant '" + nickname + "' <" + senderJID.toString() + "> is entering room "
                    + room.getRoomJID() + " as role=" + newRole.name() + ", affiliation=" + affiliation.name());
        }
        room.addOccupantByJid(senderJID, nickname, newRole);
        ghostbuster.add(senderJID, room);

        Element pe = clonePresence(element);

        room.updatePresenceByJid(null, pe);
        // if (currentOccupantJid == null) {

        // Service Sends New Occupant's Presence to All Occupants
        // Service Sends New Occupant's Presence to New Occupant
        sendPresenceToAllOccupants(room, senderJID, roomCreated, null);
        // }

        Integer maxchars = null;
        Integer maxstanzas = null;
        Integer seconds = null;
        Date since = null;
        Element hist = (xElement == null) ? null : xElement.getChild("history");

        if (hist != null) {
            maxchars = toInteger(hist.getAttributeStaticStr("maxchars"), null);
            maxstanzas = toInteger(hist.getAttributeStaticStr("maxstanzas"), null);
            seconds = toInteger(hist.getAttributeStaticStr("seconds"), null);
            since = DateUtil.parse(hist.getAttributeStaticStr("since"));
        }
        sendHistoryToUser(room, senderJID, maxchars, maxstanzas, seconds, since, writer);
        if ((room.getSubject() != null) && (room.getSubjectChangerNick() != null)
                && (room.getSubjectChangeDate() != null)) {
            Element message = new Element(Message.ELEM_NAME,
                    new String[] { Packet.TYPE_ATT, Packet.FROM_ATT, Packet.TO_ATT }, new String[] { "groupchat",
                            room.getRoomJID() + "/" + room.getSubjectChangerNick(), senderJID.toString() });

            message.addChild(new Element("subject", room.getSubject()));

            String stamp = DateUtil.formatDatetime(room.getSubjectChangeDate());
            Element delay = new Element("delay", new String[] { "xmlns", "stamp" },
                    new String[] { "urn:xmpp:delay", stamp });

            delay.setAttribute("jid", room.getRoomJID() + "/" + room.getSubjectChangerNick());

            Element x = new Element("x", new String[] { "xmlns", "stamp" },
                    new String[] { "jabber:x:delay", DateUtil.formatOld(room.getSubjectChangeDate()) });

            message.addChild(delay);
            message.addChild(x);
            writer.writeElement(message);
        }
        if (room.isRoomLocked()) {
            sendMucMessage(room, room.getOccupantsNickname(senderJID), "Room is locked. Please configure.");
        }
        if (roomCreated) {
            StringBuilder sb = new StringBuilder();

            sb.append("Welcome! You created new Multi User Chat Room.");
            if (room.isRoomLocked()) {
                sb.append(" Room is locked now. Configure it please!");
            } else if (lockNewRoom) {
                sb.append(" Room is unlocked and ready for occupants!");
            }
            sendMucMessage(room, room.getOccupantsNickname(senderJID), sb.toString());
        }
        if (room.getConfig().isLoggingEnabled()) {
            addJoinToHistory(room, new Date(), senderJID, nickname);
        }
        internalCountRoom(room.getRoomJID().toString());
    }

    /**
     * Method description
     * 
     * 
     * @param room
     * @param presenceElement
     * @param senderJID
     * 
     * @throws MUCException
     * @throws TigaseStringprepException
     */
    protected void processExit(final Room room, final Element presenceElement, final JID senderJID)
            throws MUCException, TigaseStringprepException {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Processing stanza " + presenceElement.toString());
        }
        if (room == null) {
            throw new MUCException(Authorization.ITEM_NOT_FOUND, "Unkown room");
        }

        final String leavingNickname = room.getOccupantsNickname(senderJID);

        if (leavingNickname == null) {
            // do it quietly
            // throw new MUCException(Authorization.ITEM_NOT_FOUND,
            // "Unkown occupant");
            return;
        }
        doQuit(room, senderJID);
    }

    /**
     * @param room
     * @param senderJID
     * @param maxchars
     * @param maxstanzas
     * @param seconds
     * @param since
     */
    private void sendHistoryToUser(final Room room, final JID senderJID, final Integer maxchars,
            final Integer maxstanzas, final Integer seconds, final Date since, final ElementWriter writer) {
        if (historyProvider != null) {
            historyProvider.getHistoryMessages(room, senderJID, maxchars, maxstanzas, seconds, since, writer);
        }
    }

    private void sendPresenceToAllOccupants(final Element $presence, Room room, JID senderJID,
            boolean newRoomCreated, String newNickName) throws TigaseStringprepException {

        final String occupantNickname = room.getOccupantsNickname(senderJID);
        final BareJID occupantJID = room.getOccupantsJidByNickname(occupantNickname);
        final Affiliation occupantAffiliation = room.getAffiliation(occupantJID);
        final Role occupantRole = room.getRole(occupantNickname);

        for (String destinationNickname : room.getOccupantsNicknames()) {
            for (JID destinationJID : room.getOccupantsJidsByNickname(destinationNickname)) {

                if (config.isMultiItemMode()) {
                    PresenceWrapper presence = preparePresence(destinationJID, $presence.clone(), room, senderJID,
                            newRoomCreated, newNickName);
                    writer.write(presence.packet);
                } else {
                    for (JID jid : room.getOccupantsJidsByNickname(occupantNickname)) {
                        Collection<JID> z = new ArrayList<JID>(1);
                        z.add(jid);
                        PresenceWrapper l = preparePresenceW(room, destinationJID, $presence.clone(), occupantJID,
                                z, occupantNickname, occupantAffiliation, occupantRole);
                        addCodes(l, newRoomCreated, newNickName);

                        // l.packet.getElement().setAttribute("id", "sta");
                        writer.write(l.packet);
                    }
                }

            }
        }
    }

    private void sendPresenceToAllOccupants(Room room, JID senderJID, boolean newRoomCreated, String newNickName)
            throws TigaseStringprepException {
        Element presence;

        if (newNickName != null) {
            presence = new Element("presence");
            presence.setAttribute("type", "unavailable");
        } else if (room.getOccupantsNickname(senderJID) == null) {
            presence = new Element("presence");
            presence.setAttribute("type", "unavailable");
        } else {
            presence = room.getLastPresenceCopyByJid(senderJID.getBareJID());
        }
        sendPresenceToAllOccupants(presence, room, senderJID, newRoomCreated, newNickName);
    }

    /**
     * Method description
     * 
     * 
     * @param lockNewRoom
     */
    public void setLockNewRoom(boolean lockNewRoom) {
        this.lockNewRoom = lockNewRoom;
    }

}