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