001/** 002 * 003 * Copyright 2016 Fernando Ramirez 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.muclight; 018 019import java.util.HashMap; 020import java.util.List; 021import java.util.Set; 022import java.util.concurrent.CopyOnWriteArraySet; 023 024import org.jivesoftware.smack.MessageListener; 025import org.jivesoftware.smack.SmackException.NoResponseException; 026import org.jivesoftware.smack.SmackException.NotConnectedException; 027import org.jivesoftware.smack.StanzaCollector; 028import org.jivesoftware.smack.StanzaListener; 029import org.jivesoftware.smack.XMPPConnection; 030import org.jivesoftware.smack.XMPPException.XMPPErrorException; 031import org.jivesoftware.smack.chat.ChatMessageListener; 032import org.jivesoftware.smack.filter.AndFilter; 033import org.jivesoftware.smack.filter.FromMatchesFilter; 034import org.jivesoftware.smack.filter.MessageTypeFilter; 035import org.jivesoftware.smack.filter.StanzaFilter; 036import org.jivesoftware.smack.packet.IQ; 037import org.jivesoftware.smack.packet.Message; 038import org.jivesoftware.smack.packet.Stanza; 039 040import org.jivesoftware.smackx.muclight.element.MUCLightAffiliationsIQ; 041import org.jivesoftware.smackx.muclight.element.MUCLightChangeAffiliationsIQ; 042import org.jivesoftware.smackx.muclight.element.MUCLightConfigurationIQ; 043import org.jivesoftware.smackx.muclight.element.MUCLightCreateIQ; 044import org.jivesoftware.smackx.muclight.element.MUCLightDestroyIQ; 045import org.jivesoftware.smackx.muclight.element.MUCLightGetAffiliationsIQ; 046import org.jivesoftware.smackx.muclight.element.MUCLightGetConfigsIQ; 047import org.jivesoftware.smackx.muclight.element.MUCLightGetInfoIQ; 048import org.jivesoftware.smackx.muclight.element.MUCLightInfoIQ; 049import org.jivesoftware.smackx.muclight.element.MUCLightSetConfigsIQ; 050 051import org.jxmpp.jid.EntityJid; 052import org.jxmpp.jid.Jid; 053 054/** 055 * MUCLight class. 056 * 057 * @author Fernando Ramirez 058 */ 059public class MultiUserChatLight { 060 061 public static final String NAMESPACE = "urn:xmpp:muclight:0"; 062 063 public static final String AFFILIATIONS = "#affiliations"; 064 public static final String INFO = "#info"; 065 public static final String CONFIGURATION = "#configuration"; 066 public static final String CREATE = "#create"; 067 public static final String DESTROY = "#destroy"; 068 public static final String BLOCKING = "#blocking"; 069 070 private final XMPPConnection connection; 071 private final EntityJid room; 072 073 private final Set<MessageListener> messageListeners = new CopyOnWriteArraySet<MessageListener>(); 074 075 /** 076 * This filter will match all stanzas send from the groupchat or from one if 077 * the groupchat occupants. 078 */ 079 private final StanzaFilter fromRoomFilter; 080 081 /** 082 * Same as {@link #fromRoomFilter} together with 083 * {@link MessageTypeFilter#GROUPCHAT}. 084 */ 085 private final StanzaFilter fromRoomGroupChatFilter; 086 087 private final StanzaListener messageListener; 088 089 private StanzaCollector messageCollector; 090 091 MultiUserChatLight(XMPPConnection connection, EntityJid room) { 092 this.connection = connection; 093 this.room = room; 094 095 fromRoomFilter = FromMatchesFilter.create(room); 096 fromRoomGroupChatFilter = new AndFilter(fromRoomFilter, MessageTypeFilter.GROUPCHAT); 097 098 messageListener = new StanzaListener() { 099 @Override 100 public void processStanza(Stanza packet) throws NotConnectedException { 101 Message message = (Message) packet; 102 for (MessageListener listener : messageListeners) { 103 listener.processMessage(message); 104 } 105 } 106 }; 107 108 connection.addSyncStanzaListener(messageListener, fromRoomGroupChatFilter); 109 } 110 111 /** 112 * Returns the JID of the room. 113 * 114 * @return the MUCLight room JID. 115 */ 116 public EntityJid getRoom() { 117 return room; 118 } 119 120 /** 121 * Sends a message to the chat room. 122 * 123 * @param text 124 * the text of the message to send. 125 * @throws NotConnectedException 126 * @throws InterruptedException 127 */ 128 public void sendMessage(String text) throws NotConnectedException, InterruptedException { 129 Message message = createMessage(); 130 message.setBody(text); 131 connection.sendStanza(message); 132 } 133 134 /** 135 * Returns a new Chat for sending private messages to a given room occupant. 136 * The Chat's occupant address is the room's JID (i.e. 137 * roomName@service/nick). The server service will change the 'from' address 138 * to the sender's room JID and delivering the message to the intended 139 * recipient's full JID. 140 * 141 * @param occupant 142 * occupant unique room JID (e.g. 143 * 'darkcave@macbeth.shakespeare.lit/Paul'). 144 * @param listener 145 * the listener is a message listener that will handle messages 146 * for the newly created chat. 147 * @return new Chat for sending private messages to a given room occupant. 148 */ 149 @SuppressWarnings("deprecation") 150 @Deprecated 151 // Do not re-use Chat API, which was designed for XMPP-IM 1:1 chats and not MUClight private chats. 152 public org.jivesoftware.smack.chat.Chat createPrivateChat(EntityJid occupant, ChatMessageListener listener) { 153 return org.jivesoftware.smack.chat.ChatManager.getInstanceFor(connection).createChat(occupant, listener); 154 } 155 156 /** 157 * Creates a new Message to send to the chat room. 158 * 159 * @return a new Message addressed to the chat room. 160 */ 161 public Message createMessage() { 162 return new Message(room, Message.Type.groupchat); 163 } 164 165 /** 166 * Sends a Message to the chat room. 167 * 168 * @param message 169 * the message. 170 * @throws NotConnectedException 171 * @throws InterruptedException 172 */ 173 public void sendMessage(Message message) throws NotConnectedException, InterruptedException { 174 message.setTo(room); 175 message.setType(Message.Type.groupchat); 176 connection.sendStanza(message); 177 } 178 179 /** 180 * Polls for and returns the next message. 181 * 182 * @return the next message if one is immediately available 183 */ 184 public Message pollMessage() { 185 return messageCollector.pollResult(); 186 } 187 188 /** 189 * Returns the next available message in the chat. The method call will 190 * block (not return) until a message is available. 191 * 192 * @return the next message. 193 * @throws InterruptedException 194 */ 195 public Message nextMessage() throws InterruptedException { 196 return messageCollector.nextResult(); 197 } 198 199 /** 200 * Returns the next available message in the chat. 201 * 202 * @param timeout 203 * the maximum amount of time to wait for the next message. 204 * @return the next message, or null if the timeout elapses without a 205 * message becoming available. 206 * @throws InterruptedException 207 */ 208 public Message nextMessage(long timeout) throws InterruptedException { 209 return messageCollector.nextResult(timeout); 210 } 211 212 /** 213 * Adds a stanza listener that will be notified of any new messages 214 * in the group chat. Only "group chat" messages addressed to this group 215 * chat will be delivered to the listener. 216 * 217 * @param listener 218 * a stanza listener. 219 * @return true if the listener was not already added. 220 */ 221 public boolean addMessageListener(MessageListener listener) { 222 return messageListeners.add(listener); 223 } 224 225 /** 226 * Removes a stanza listener that was being notified of any new 227 * messages in the MUCLight. Only "group chat" messages addressed to this 228 * MUCLight were being delivered to the listener. 229 * 230 * @param listener 231 * a stanza listener. 232 * @return true if the listener was removed, otherwise the listener was not 233 * added previously. 234 */ 235 public boolean removeMessageListener(MessageListener listener) { 236 return messageListeners.remove(listener); 237 } 238 239 /** 240 * Remove the connection callbacks used by this MUC Light from the 241 * connection. 242 */ 243 private void removeConnectionCallbacks() { 244 connection.removeSyncStanzaListener(messageListener); 245 if (messageCollector != null) { 246 messageCollector.cancel(); 247 messageCollector = null; 248 } 249 } 250 251 @Override 252 public String toString() { 253 return "MUC Light: " + room + "(" + connection.getUser() + ")"; 254 } 255 256 /** 257 * Create new MUCLight. 258 * 259 * @param roomName 260 * @param subject 261 * @param customConfigs 262 * @param occupants 263 * @throws Exception 264 */ 265 public void create(String roomName, String subject, HashMap<String, String> customConfigs, List<Jid> occupants) 266 throws Exception { 267 MUCLightCreateIQ createMUCLightIQ = new MUCLightCreateIQ(room, roomName, occupants); 268 269 messageCollector = connection.createStanzaCollector(fromRoomGroupChatFilter); 270 271 try { 272 connection.createStanzaCollectorAndSend(createMUCLightIQ).nextResultOrThrow(); 273 } catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) { 274 removeConnectionCallbacks(); 275 throw e; 276 } 277 } 278 279 /** 280 * Create new MUCLight. 281 * 282 * @param roomName 283 * @param occupants 284 * @throws Exception 285 */ 286 public void create(String roomName, List<Jid> occupants) throws Exception { 287 create(roomName, null, null, occupants); 288 } 289 290 /** 291 * Leave the MUCLight. 292 * 293 * @throws NotConnectedException 294 * @throws InterruptedException 295 * @throws NoResponseException 296 * @throws XMPPErrorException 297 */ 298 public void leave() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 299 HashMap<Jid, MUCLightAffiliation> affiliations = new HashMap<>(); 300 affiliations.put(connection.getUser(), MUCLightAffiliation.none); 301 302 MUCLightChangeAffiliationsIQ changeAffiliationsIQ = new MUCLightChangeAffiliationsIQ(room, affiliations); 303 IQ responseIq = connection.createStanzaCollectorAndSend(changeAffiliationsIQ).nextResultOrThrow(); 304 boolean roomLeft = responseIq.getType().equals(IQ.Type.result); 305 306 if (roomLeft) { 307 removeConnectionCallbacks(); 308 } 309 } 310 311 /** 312 * Get the MUC Light info. 313 * 314 * @param version 315 * @return the room info 316 * @throws NoResponseException 317 * @throws XMPPErrorException 318 * @throws NotConnectedException 319 * @throws InterruptedException 320 */ 321 public MUCLightRoomInfo getFullInfo(String version) 322 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 323 MUCLightGetInfoIQ mucLightGetInfoIQ = new MUCLightGetInfoIQ(room, version); 324 325 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightGetInfoIQ).nextResultOrThrow(); 326 MUCLightInfoIQ mucLightInfoResponseIQ = (MUCLightInfoIQ) responseIq; 327 328 return new MUCLightRoomInfo(mucLightInfoResponseIQ.getVersion(), room, 329 mucLightInfoResponseIQ.getConfiguration(), mucLightInfoResponseIQ.getOccupants()); 330 } 331 332 /** 333 * Get the MUC Light info. 334 * 335 * @return the room info 336 * @throws NoResponseException 337 * @throws XMPPErrorException 338 * @throws NotConnectedException 339 * @throws InterruptedException 340 */ 341 public MUCLightRoomInfo getFullInfo() 342 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 343 return getFullInfo(null); 344 } 345 346 /** 347 * Get the MUC Light configuration. 348 * 349 * @param version 350 * @return the room configuration 351 * @throws NoResponseException 352 * @throws XMPPErrorException 353 * @throws NotConnectedException 354 * @throws InterruptedException 355 */ 356 public MUCLightRoomConfiguration getConfiguration(String version) 357 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 358 MUCLightGetConfigsIQ mucLightGetConfigsIQ = new MUCLightGetConfigsIQ(room, version); 359 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightGetConfigsIQ).nextResultOrThrow(); 360 MUCLightConfigurationIQ mucLightConfigurationIQ = (MUCLightConfigurationIQ) responseIq; 361 return mucLightConfigurationIQ.getConfiguration(); 362 } 363 364 /** 365 * Get the MUC Light configuration. 366 * 367 * @return the room configuration 368 * @throws NoResponseException 369 * @throws XMPPErrorException 370 * @throws NotConnectedException 371 * @throws InterruptedException 372 */ 373 public MUCLightRoomConfiguration getConfiguration() 374 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 375 return getConfiguration(null); 376 } 377 378 /** 379 * Get the MUC Light affiliations. 380 * 381 * @param version 382 * @return the room affiliations 383 * @throws NoResponseException 384 * @throws XMPPErrorException 385 * @throws NotConnectedException 386 * @throws InterruptedException 387 */ 388 public HashMap<Jid, MUCLightAffiliation> getAffiliations(String version) 389 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 390 MUCLightGetAffiliationsIQ mucLightGetAffiliationsIQ = new MUCLightGetAffiliationsIQ(room, version); 391 392 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightGetAffiliationsIQ).nextResultOrThrow(); 393 MUCLightAffiliationsIQ mucLightAffiliationsIQ = (MUCLightAffiliationsIQ) responseIq; 394 395 return mucLightAffiliationsIQ.getAffiliations(); 396 } 397 398 /** 399 * Get the MUC Light affiliations. 400 * 401 * @return the room affiliations 402 * @throws NoResponseException 403 * @throws XMPPErrorException 404 * @throws NotConnectedException 405 * @throws InterruptedException 406 */ 407 public HashMap<Jid, MUCLightAffiliation> getAffiliations() 408 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 409 return getAffiliations(null); 410 } 411 412 /** 413 * Change the MUC Light affiliations. 414 * 415 * @param affiliations 416 * @throws NoResponseException 417 * @throws XMPPErrorException 418 * @throws NotConnectedException 419 * @throws InterruptedException 420 */ 421 public void changeAffiliations(HashMap<Jid, MUCLightAffiliation> affiliations) 422 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 423 MUCLightChangeAffiliationsIQ changeAffiliationsIQ = new MUCLightChangeAffiliationsIQ(room, affiliations); 424 connection.createStanzaCollectorAndSend(changeAffiliationsIQ).nextResultOrThrow(); 425 } 426 427 /** 428 * Destroy the MUC Light. Only will work if it is requested by the owner. 429 * 430 * @throws NoResponseException 431 * @throws XMPPErrorException 432 * @throws NotConnectedException 433 * @throws InterruptedException 434 */ 435 public void destroy() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 436 MUCLightDestroyIQ mucLightDestroyIQ = new MUCLightDestroyIQ(room); 437 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightDestroyIQ).nextResultOrThrow(); 438 boolean roomDestroyed = responseIq.getType().equals(IQ.Type.result); 439 440 if (roomDestroyed) { 441 removeConnectionCallbacks(); 442 } 443 } 444 445 /** 446 * Change the subject of the MUC Light. 447 * 448 * @param subject 449 * @throws NoResponseException 450 * @throws XMPPErrorException 451 * @throws NotConnectedException 452 * @throws InterruptedException 453 */ 454 public void changeSubject(String subject) 455 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 456 MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, null, subject, null); 457 connection.createStanzaCollectorAndSend(mucLightSetConfigIQ).nextResultOrThrow(); 458 } 459 460 /** 461 * Change the name of the room. 462 * 463 * @param roomName 464 * @throws NoResponseException 465 * @throws XMPPErrorException 466 * @throws NotConnectedException 467 * @throws InterruptedException 468 */ 469 public void changeRoomName(String roomName) 470 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 471 MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, roomName, null); 472 connection.createStanzaCollectorAndSend(mucLightSetConfigIQ).nextResultOrThrow(); 473 } 474 475 /** 476 * Set the room configurations. 477 * 478 * @param customConfigs 479 * @throws NoResponseException 480 * @throws XMPPErrorException 481 * @throws NotConnectedException 482 * @throws InterruptedException 483 */ 484 public void setRoomConfigs(HashMap<String, String> customConfigs) 485 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 486 setRoomConfigs(null, customConfigs); 487 } 488 489 /** 490 * Set the room configurations. 491 * 492 * @param roomName 493 * @param customConfigs 494 * @throws NoResponseException 495 * @throws XMPPErrorException 496 * @throws NotConnectedException 497 * @throws InterruptedException 498 */ 499 public void setRoomConfigs(String roomName, HashMap<String, String> customConfigs) 500 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 501 MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, roomName, customConfigs); 502 connection.createStanzaCollectorAndSend(mucLightSetConfigIQ).nextResultOrThrow(); 503 } 504 505}