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