MultiUserChatLight.java

  1. /**
  2.  *
  3.  * Copyright 2016 Fernando Ramirez
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.muclight;

  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Set;
  21. import java.util.concurrent.CopyOnWriteArraySet;

  22. import org.jivesoftware.smack.MessageListener;
  23. import org.jivesoftware.smack.SmackException.NoResponseException;
  24. import org.jivesoftware.smack.SmackException.NotConnectedException;
  25. import org.jivesoftware.smack.StanzaCollector;
  26. import org.jivesoftware.smack.StanzaListener;
  27. import org.jivesoftware.smack.XMPPConnection;
  28. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  29. import org.jivesoftware.smack.chat.ChatMessageListener;
  30. import org.jivesoftware.smack.filter.AndFilter;
  31. import org.jivesoftware.smack.filter.FromMatchesFilter;
  32. import org.jivesoftware.smack.filter.MessageTypeFilter;
  33. import org.jivesoftware.smack.filter.StanzaFilter;
  34. import org.jivesoftware.smack.packet.IQ;
  35. import org.jivesoftware.smack.packet.Message;
  36. import org.jivesoftware.smack.packet.MessageBuilder;
  37. import org.jivesoftware.smack.packet.Stanza;

  38. import org.jivesoftware.smackx.muclight.element.MUCLightAffiliationsIQ;
  39. import org.jivesoftware.smackx.muclight.element.MUCLightChangeAffiliationsIQ;
  40. import org.jivesoftware.smackx.muclight.element.MUCLightConfigurationIQ;
  41. import org.jivesoftware.smackx.muclight.element.MUCLightCreateIQ;
  42. import org.jivesoftware.smackx.muclight.element.MUCLightDestroyIQ;
  43. import org.jivesoftware.smackx.muclight.element.MUCLightGetAffiliationsIQ;
  44. import org.jivesoftware.smackx.muclight.element.MUCLightGetConfigsIQ;
  45. import org.jivesoftware.smackx.muclight.element.MUCLightGetInfoIQ;
  46. import org.jivesoftware.smackx.muclight.element.MUCLightInfoIQ;
  47. import org.jivesoftware.smackx.muclight.element.MUCLightSetConfigsIQ;

  48. import org.jxmpp.jid.EntityJid;
  49. import org.jxmpp.jid.Jid;

  50. /**
  51.  * MUCLight class.
  52.  *
  53.  * @author Fernando Ramirez
  54.  */
  55. public class MultiUserChatLight {

  56.     public static final String NAMESPACE = "urn:xmpp:muclight:0";

  57.     public static final String AFFILIATIONS = "#affiliations";
  58.     public static final String INFO = "#info";
  59.     public static final String CONFIGURATION = "#configuration";
  60.     public static final String CREATE = "#create";
  61.     public static final String DESTROY = "#destroy";
  62.     public static final String BLOCKING = "#blocking";

  63.     private final XMPPConnection connection;
  64.     private final EntityJid room;

  65.     private final Set<MessageListener> messageListeners = new CopyOnWriteArraySet<MessageListener>();

  66.     /**
  67.      * This filter will match all stanzas send from the groupchat or from one if
  68.      * the groupchat occupants.
  69.      */
  70.     private final StanzaFilter fromRoomFilter;

  71.     /**
  72.      * Same as {@link #fromRoomFilter} together with
  73.      * {@link MessageTypeFilter#GROUPCHAT}.
  74.      */
  75.     private final StanzaFilter fromRoomGroupChatFilter;

  76.     private final StanzaListener messageListener;

  77.     private StanzaCollector messageCollector;

  78.     MultiUserChatLight(XMPPConnection connection, EntityJid room) {
  79.         this.connection = connection;
  80.         this.room = room;

  81.         fromRoomFilter = FromMatchesFilter.create(room);
  82.         fromRoomGroupChatFilter = new AndFilter(fromRoomFilter, MessageTypeFilter.GROUPCHAT);

  83.         messageListener = new StanzaListener() {
  84.             @Override
  85.             public void processStanza(Stanza packet) throws NotConnectedException {
  86.                 Message message = (Message) packet;
  87.                 for (MessageListener listener : messageListeners) {
  88.                     listener.processMessage(message);
  89.                 }
  90.             }
  91.         };

  92.         connection.addSyncStanzaListener(messageListener, fromRoomGroupChatFilter);
  93.     }

  94.     /**
  95.      * Returns the JID of the room.
  96.      *
  97.      * @return the MUCLight room JID.
  98.      */
  99.     public EntityJid getRoom() {
  100.         return room;
  101.     }

  102.     /**
  103.      * Sends a message to the chat room.
  104.      *
  105.      * @param text TODO javadoc me please
  106.      *            the text of the message to send.
  107.      * @throws NotConnectedException if the XMPP connection is not connected.
  108.      * @throws InterruptedException if the calling thread was interrupted.
  109.      */
  110.     public void sendMessage(String text) throws NotConnectedException, InterruptedException {
  111.         MessageBuilder message = buildMessage();
  112.         message.setBody(text);
  113.         connection.sendStanza(message.build());
  114.     }

  115.     /**
  116.      * Returns a new Chat for sending private messages to a given room occupant.
  117.      * The Chat's occupant address is the room's JID (i.e.
  118.      * roomName@service/nick). The server service will change the 'from' address
  119.      * to the sender's room JID and delivering the message to the intended
  120.      * recipient's full JID.
  121.      *
  122.      * @param occupant TODO javadoc me please
  123.      *            occupant unique room JID (e.g.
  124.      *            'darkcave@macbeth.shakespeare.lit/Paul').
  125.      * @param listener TODO javadoc me please
  126.      *            the listener is a message listener that will handle messages
  127.      *            for the newly created chat.
  128.      * @return new Chat for sending private messages to a given room occupant.
  129.      */
  130.     @Deprecated
  131.     // Do not re-use Chat API, which was designed for XMPP-IM 1:1 chats and not MUClight private chats.
  132.     public org.jivesoftware.smack.chat.Chat createPrivateChat(EntityJid occupant, ChatMessageListener listener) {
  133.         return org.jivesoftware.smack.chat.ChatManager.getInstanceFor(connection).createChat(occupant, listener);
  134.     }

  135.     /**
  136.      * Creates a new Message to send to the chat room.
  137.      *
  138.      * @return a new Message addressed to the chat room.
  139.      * @deprecated use {@link #buildMessage()} instead.
  140.      */
  141.     @Deprecated
  142.     // TODO: Remove when stanza builder is ready.
  143.     public Message createMessage() {
  144.         return connection.getStanzaFactory().buildMessageStanza()
  145.                 .ofType(Message.Type.groupchat)
  146.                 .to(room)
  147.                 .build();
  148.     }

  149.     /**
  150.      * Constructs a new message builder for messages send to this MUC room.
  151.      *
  152.      * @return a new message builder.
  153.      */
  154.     public MessageBuilder buildMessage() {
  155.         return connection.getStanzaFactory()
  156.                 .buildMessageStanza()
  157.                 .ofType(Message.Type.groupchat)
  158.                 .to(room)
  159.                 ;
  160.     }

  161.     /**
  162.      * Sends a Message to the chat room.
  163.      *
  164.      * @param messageBuilder the message.
  165.      * @throws NotConnectedException if the XMPP connection is not connected.
  166.      * @throws InterruptedException if the calling thread was interrupted.
  167.      */
  168.     public void sendMessage(MessageBuilder messageBuilder) throws NotConnectedException, InterruptedException {
  169.         Message message = messageBuilder.to(room).ofType(Message.Type.groupchat).build();
  170.         connection.sendStanza(message);
  171.     }

  172.     /**
  173.      * Polls for and returns the next message.
  174.      *
  175.      * @return the next message if one is immediately available
  176.      */
  177.     public Message pollMessage() {
  178.         return messageCollector.pollResult();
  179.     }

  180.     /**
  181.      * Returns the next available message in the chat. The method call will
  182.      * block (not return) until a message is available.
  183.      *
  184.      * @return the next message.
  185.      * @throws InterruptedException if the calling thread was interrupted.
  186.      */
  187.     public Message nextMessage() throws InterruptedException {
  188.         return messageCollector.nextResultBlockForever();
  189.     }

  190.     /**
  191.      * Returns the next available message in the chat.
  192.      *
  193.      * @param timeout TODO javadoc me please
  194.      *            the maximum amount of time to wait for the next message.
  195.      * @return the next message, or null if the timeout elapses without a
  196.      *         message becoming available.
  197.      * @throws InterruptedException if the calling thread was interrupted.
  198.      */
  199.     public Message nextMessage(long timeout) throws InterruptedException {
  200.         return messageCollector.nextResult(timeout);
  201.     }

  202.     /**
  203.      * Adds a stanza listener that will be notified of any new messages
  204.      * in the group chat. Only "group chat" messages addressed to this group
  205.      * chat will be delivered to the listener.
  206.      *
  207.      * @param listener TODO javadoc me please
  208.      *            a stanza listener.
  209.      * @return true if the listener was not already added.
  210.      */
  211.     public boolean addMessageListener(MessageListener listener) {
  212.         return messageListeners.add(listener);
  213.     }

  214.     /**
  215.      * Removes a stanza listener that was being notified of any new
  216.      * messages in the MUCLight. Only "group chat" messages addressed to this
  217.      * MUCLight were being delivered to the listener.
  218.      *
  219.      * @param listener TODO javadoc me please
  220.      *            a stanza listener.
  221.      * @return true if the listener was removed, otherwise the listener was not
  222.      *         added previously.
  223.      */
  224.     public boolean removeMessageListener(MessageListener listener) {
  225.         return messageListeners.remove(listener);
  226.     }

  227.     /**
  228.      * Remove the connection callbacks used by this MUC Light from the
  229.      * connection.
  230.      */
  231.     private void removeConnectionCallbacks() {
  232.         connection.removeSyncStanzaListener(messageListener);
  233.         if (messageCollector != null) {
  234.             messageCollector.cancel();
  235.             messageCollector = null;
  236.         }
  237.     }

  238.     @Override
  239.     public String toString() {
  240.         return "MUC Light: " + room + "(" + connection.getUser() + ")";
  241.     }

  242.     /**
  243.      * Create new MUCLight.
  244.      *
  245.      * @param roomName TODO javadoc me please
  246.      * @param subject TODO javadoc me please
  247.      * @param customConfigs TODO javadoc me please
  248.      * @param occupants TODO javadoc me please
  249.      * @throws Exception TODO javadoc me please
  250.      */
  251.     public void create(String roomName, String subject, HashMap<String, String> customConfigs, List<Jid> occupants)
  252.             throws Exception {
  253.         MUCLightCreateIQ createMUCLightIQ = new MUCLightCreateIQ(room, roomName, occupants);

  254.         messageCollector = connection.createStanzaCollector(fromRoomGroupChatFilter);

  255.         try {
  256.             connection.sendIqRequestAndWaitForResponse(createMUCLightIQ);
  257.         } catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) {
  258.             removeConnectionCallbacks();
  259.             throw e;
  260.         }
  261.     }

  262.     /**
  263.      * Create new MUCLight.
  264.      *
  265.      * @param roomName TODO javadoc me please
  266.      * @param occupants TODO javadoc me please
  267.      * @throws Exception TODO javadoc me please
  268.      */
  269.     public void create(String roomName, List<Jid> occupants) throws Exception {
  270.         create(roomName, null, null, occupants);
  271.     }

  272.     /**
  273.      * Leave the MUCLight.
  274.      *
  275.      * @throws NotConnectedException if the XMPP connection is not connected.
  276.      * @throws InterruptedException if the calling thread was interrupted.
  277.      * @throws NoResponseException if there was no response from the remote entity.
  278.      * @throws XMPPErrorException if there was an XMPP error returned.
  279.      */
  280.     public void leave() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
  281.         HashMap<Jid, MUCLightAffiliation> affiliations = new HashMap<>();
  282.         affiliations.put(connection.getUser(), MUCLightAffiliation.none);

  283.         MUCLightChangeAffiliationsIQ changeAffiliationsIQ = new MUCLightChangeAffiliationsIQ(room, affiliations);
  284.         IQ responseIq = connection.sendIqRequestAndWaitForResponse(changeAffiliationsIQ);
  285.         boolean roomLeft = responseIq.getType().equals(IQ.Type.result);

  286.         if (roomLeft) {
  287.             removeConnectionCallbacks();
  288.         }
  289.     }

  290.     /**
  291.      * Get the MUC Light info.
  292.      *
  293.      * @param version TODO javadoc me please
  294.      * @return the room info
  295.      * @throws NoResponseException if there was no response from the remote entity.
  296.      * @throws XMPPErrorException if there was an XMPP error returned.
  297.      * @throws NotConnectedException if the XMPP connection is not connected.
  298.      * @throws InterruptedException if the calling thread was interrupted.
  299.      */
  300.     public MUCLightRoomInfo getFullInfo(String version)
  301.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  302.         MUCLightGetInfoIQ mucLightGetInfoIQ = new MUCLightGetInfoIQ(room, version);

  303.         IQ responseIq = connection.sendIqRequestAndWaitForResponse(mucLightGetInfoIQ);
  304.         MUCLightInfoIQ mucLightInfoResponseIQ = (MUCLightInfoIQ) responseIq;

  305.         return new MUCLightRoomInfo(mucLightInfoResponseIQ.getVersion(), room,
  306.                 mucLightInfoResponseIQ.getConfiguration(), mucLightInfoResponseIQ.getOccupants());
  307.     }

  308.     /**
  309.      * Get the MUC Light info.
  310.      *
  311.      * @return the room info
  312.      * @throws NoResponseException if there was no response from the remote entity.
  313.      * @throws XMPPErrorException if there was an XMPP error returned.
  314.      * @throws NotConnectedException if the XMPP connection is not connected.
  315.      * @throws InterruptedException if the calling thread was interrupted.
  316.      */
  317.     public MUCLightRoomInfo getFullInfo()
  318.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  319.         return getFullInfo(null);
  320.     }

  321.     /**
  322.      * Get the MUC Light configuration.
  323.      *
  324.      * @param version TODO javadoc me please
  325.      * @return the room configuration
  326.      * @throws NoResponseException if there was no response from the remote entity.
  327.      * @throws XMPPErrorException if there was an XMPP error returned.
  328.      * @throws NotConnectedException if the XMPP connection is not connected.
  329.      * @throws InterruptedException if the calling thread was interrupted.
  330.      */
  331.     public MUCLightRoomConfiguration getConfiguration(String version)
  332.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  333.         MUCLightGetConfigsIQ mucLightGetConfigsIQ = new MUCLightGetConfigsIQ(room, version);
  334.         IQ responseIq = connection.sendIqRequestAndWaitForResponse(mucLightGetConfigsIQ);
  335.         MUCLightConfigurationIQ mucLightConfigurationIQ = (MUCLightConfigurationIQ) responseIq;
  336.         return mucLightConfigurationIQ.getConfiguration();
  337.     }

  338.     /**
  339.      * Get the MUC Light configuration.
  340.      *
  341.      * @return the room configuration
  342.      * @throws NoResponseException if there was no response from the remote entity.
  343.      * @throws XMPPErrorException if there was an XMPP error returned.
  344.      * @throws NotConnectedException if the XMPP connection is not connected.
  345.      * @throws InterruptedException if the calling thread was interrupted.
  346.      */
  347.     public MUCLightRoomConfiguration getConfiguration()
  348.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  349.         return getConfiguration(null);
  350.     }

  351.     /**
  352.      * Get the MUC Light affiliations.
  353.      *
  354.      * @param version TODO javadoc me please
  355.      * @return the room affiliations
  356.      * @throws NoResponseException if there was no response from the remote entity.
  357.      * @throws XMPPErrorException if there was an XMPP error returned.
  358.      * @throws NotConnectedException if the XMPP connection is not connected.
  359.      * @throws InterruptedException if the calling thread was interrupted.
  360.      */
  361.     public HashMap<Jid, MUCLightAffiliation> getAffiliations(String version)
  362.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  363.         MUCLightGetAffiliationsIQ mucLightGetAffiliationsIQ = new MUCLightGetAffiliationsIQ(room, version);

  364.         IQ responseIq = connection.sendIqRequestAndWaitForResponse(mucLightGetAffiliationsIQ);
  365.         MUCLightAffiliationsIQ mucLightAffiliationsIQ = (MUCLightAffiliationsIQ) responseIq;

  366.         return mucLightAffiliationsIQ.getAffiliations();
  367.     }

  368.     /**
  369.      * Get the MUC Light affiliations.
  370.      *
  371.      * @return the room affiliations
  372.      * @throws NoResponseException if there was no response from the remote entity.
  373.      * @throws XMPPErrorException if there was an XMPP error returned.
  374.      * @throws NotConnectedException if the XMPP connection is not connected.
  375.      * @throws InterruptedException if the calling thread was interrupted.
  376.      */
  377.     public HashMap<Jid, MUCLightAffiliation> getAffiliations()
  378.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  379.         return getAffiliations(null);
  380.     }

  381.     /**
  382.      * Change the MUC Light affiliations.
  383.      *
  384.      * @param affiliations TODO javadoc me please
  385.      * @throws NoResponseException if there was no response from the remote entity.
  386.      * @throws XMPPErrorException if there was an XMPP error returned.
  387.      * @throws NotConnectedException if the XMPP connection is not connected.
  388.      * @throws InterruptedException if the calling thread was interrupted.
  389.      */
  390.     public void changeAffiliations(HashMap<Jid, MUCLightAffiliation> affiliations)
  391.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  392.         MUCLightChangeAffiliationsIQ changeAffiliationsIQ = new MUCLightChangeAffiliationsIQ(room, affiliations);
  393.         connection.sendIqRequestAndWaitForResponse(changeAffiliationsIQ);
  394.     }

  395.     /**
  396.      * Destroy the MUC Light. Only will work if it is requested by the owner.
  397.      *
  398.      * @throws NoResponseException if there was no response from the remote entity.
  399.      * @throws XMPPErrorException if there was an XMPP error returned.
  400.      * @throws NotConnectedException if the XMPP connection is not connected.
  401.      * @throws InterruptedException if the calling thread was interrupted.
  402.      */
  403.     public void destroy() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  404.         MUCLightDestroyIQ mucLightDestroyIQ = new MUCLightDestroyIQ(room);
  405.         IQ responseIq = connection.sendIqRequestAndWaitForResponse(mucLightDestroyIQ);
  406.         boolean roomDestroyed = responseIq.getType().equals(IQ.Type.result);

  407.         if (roomDestroyed) {
  408.             removeConnectionCallbacks();
  409.         }
  410.     }

  411.     /**
  412.      * Change the subject of the MUC Light.
  413.      *
  414.      * @param subject TODO javadoc me please
  415.      * @throws NoResponseException if there was no response from the remote entity.
  416.      * @throws XMPPErrorException if there was an XMPP error returned.
  417.      * @throws NotConnectedException if the XMPP connection is not connected.
  418.      * @throws InterruptedException if the calling thread was interrupted.
  419.      */
  420.     public void changeSubject(String subject)
  421.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  422.         MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, null, subject, null);
  423.         connection.sendIqRequestAndWaitForResponse(mucLightSetConfigIQ);
  424.     }

  425.     /**
  426.      * Change the name of the room.
  427.      *
  428.      * @param roomName TODO javadoc me please
  429.      * @throws NoResponseException if there was no response from the remote entity.
  430.      * @throws XMPPErrorException if there was an XMPP error returned.
  431.      * @throws NotConnectedException if the XMPP connection is not connected.
  432.      * @throws InterruptedException if the calling thread was interrupted.
  433.      */
  434.     public void changeRoomName(String roomName)
  435.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  436.         MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, roomName, null);
  437.         connection.sendIqRequestAndWaitForResponse(mucLightSetConfigIQ);
  438.     }

  439.     /**
  440.      * Set the room configurations.
  441.      *
  442.      * @param customConfigs TODO javadoc me please
  443.      * @throws NoResponseException if there was no response from the remote entity.
  444.      * @throws XMPPErrorException if there was an XMPP error returned.
  445.      * @throws NotConnectedException if the XMPP connection is not connected.
  446.      * @throws InterruptedException if the calling thread was interrupted.
  447.      */
  448.     public void setRoomConfigs(HashMap<String, String> customConfigs)
  449.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  450.         setRoomConfigs(null, customConfigs);
  451.     }

  452.     /**
  453.      * Set the room configurations.
  454.      *
  455.      * @param roomName TODO javadoc me please
  456.      * @param customConfigs TODO javadoc me please
  457.      * @throws NoResponseException if there was no response from the remote entity.
  458.      * @throws XMPPErrorException if there was an XMPP error returned.
  459.      * @throws NotConnectedException if the XMPP connection is not connected.
  460.      * @throws InterruptedException if the calling thread was interrupted.
  461.      */
  462.     public void setRoomConfigs(String roomName, HashMap<String, String> customConfigs)
  463.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  464.         MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, roomName, customConfigs);
  465.         connection.sendIqRequestAndWaitForResponse(mucLightSetConfigIQ);
  466.     }

  467. }