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 @Deprecated 150 // Do not re-use Chat API, which was designed for XMPP-IM 1:1 chats and not MUClight private chats. 151 public org.jivesoftware.smack.chat.Chat createPrivateChat(EntityJid occupant, ChatMessageListener listener) { 152 return org.jivesoftware.smack.chat.ChatManager.getInstanceFor(connection).createChat(occupant, listener); 153 } 154 155 /** 156 * Creates a new Message to send to the chat room. 157 * 158 * @return a new Message addressed to the chat room. 159 */ 160 public Message createMessage() { 161 return new Message(room, Message.Type.groupchat); 162 } 163 164 /** 165 * Sends a Message to the chat room. 166 * 167 * @param message 168 * the message. 169 * @throws NotConnectedException 170 * @throws InterruptedException 171 */ 172 public void sendMessage(Message message) throws NotConnectedException, InterruptedException { 173 message.setTo(room); 174 message.setType(Message.Type.groupchat); 175 connection.sendStanza(message); 176 } 177 178 /** 179 * Polls for and returns the next message. 180 * 181 * @return the next message if one is immediately available 182 */ 183 public Message pollMessage() { 184 return messageCollector.pollResult(); 185 } 186 187 /** 188 * Returns the next available message in the chat. The method call will 189 * block (not return) until a message is available. 190 * 191 * @return the next message. 192 * @throws InterruptedException 193 */ 194 public Message nextMessage() throws InterruptedException { 195 return messageCollector.nextResult(); 196 } 197 198 /** 199 * Returns the next available message in the chat. 200 * 201 * @param timeout 202 * the maximum amount of time to wait for the next message. 203 * @return the next message, or null if the timeout elapses without a 204 * message becoming available. 205 * @throws InterruptedException 206 */ 207 public Message nextMessage(long timeout) throws InterruptedException { 208 return messageCollector.nextResult(timeout); 209 } 210 211 /** 212 * Adds a stanza(/packet) listener that will be notified of any new messages 213 * in the group chat. Only "group chat" messages addressed to this group 214 * chat will be delivered to the listener. 215 * 216 * @param listener 217 * a stanza(/packet) listener. 218 * @return true if the listener was not already added. 219 */ 220 public boolean addMessageListener(MessageListener listener) { 221 return messageListeners.add(listener); 222 } 223 224 /** 225 * Removes a stanza(/packet) listener that was being notified of any new 226 * messages in the MUCLight. Only "group chat" messages addressed to this 227 * MUCLight were being delivered to the listener. 228 * 229 * @param listener 230 * a stanza(/packet) listener. 231 * @return true if the listener was removed, otherwise the listener was not 232 * added previously. 233 */ 234 public boolean removeMessageListener(MessageListener listener) { 235 return messageListeners.remove(listener); 236 } 237 238 /** 239 * Remove the connection callbacks used by this MUC Light from the 240 * connection. 241 */ 242 private void removeConnectionCallbacks() { 243 connection.removeSyncStanzaListener(messageListener); 244 if (messageCollector != null) { 245 messageCollector.cancel(); 246 messageCollector = null; 247 } 248 } 249 250 @Override 251 public String toString() { 252 return "MUC Light: " + room + "(" + connection.getUser() + ")"; 253 } 254 255 /** 256 * Create new MUCLight. 257 * 258 * @param roomName 259 * @param subject 260 * @param customConfigs 261 * @param occupants 262 * @throws Exception 263 */ 264 public void create(String roomName, String subject, HashMap<String, String> customConfigs, List<Jid> occupants) 265 throws Exception { 266 MUCLightCreateIQ createMUCLightIQ = new MUCLightCreateIQ(room, roomName, occupants); 267 268 messageCollector = connection.createStanzaCollector(fromRoomGroupChatFilter); 269 270 try { 271 connection.createStanzaCollectorAndSend(createMUCLightIQ).nextResultOrThrow(); 272 } catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) { 273 removeConnectionCallbacks(); 274 throw e; 275 } 276 } 277 278 /** 279 * Create new MUCLight. 280 * 281 * @param roomName 282 * @param occupants 283 * @throws Exception 284 */ 285 public void create(String roomName, List<Jid> occupants) throws Exception { 286 create(roomName, null, null, occupants); 287 } 288 289 /** 290 * Leave the MUCLight. 291 * 292 * @throws NotConnectedException 293 * @throws InterruptedException 294 * @throws NoResponseException 295 * @throws XMPPErrorException 296 */ 297 public void leave() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 298 HashMap<Jid, MUCLightAffiliation> affiliations = new HashMap<>(); 299 affiliations.put(connection.getUser(), MUCLightAffiliation.none); 300 301 MUCLightChangeAffiliationsIQ changeAffiliationsIQ = new MUCLightChangeAffiliationsIQ(room, affiliations); 302 IQ responseIq = connection.createStanzaCollectorAndSend(changeAffiliationsIQ).nextResultOrThrow(); 303 boolean roomLeft = responseIq.getType().equals(IQ.Type.result); 304 305 if (roomLeft) { 306 removeConnectionCallbacks(); 307 } 308 } 309 310 /** 311 * Get the MUC Light info. 312 * 313 * @param version 314 * @return the room info 315 * @throws NoResponseException 316 * @throws XMPPErrorException 317 * @throws NotConnectedException 318 * @throws InterruptedException 319 */ 320 public MUCLightRoomInfo getFullInfo(String version) 321 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 322 MUCLightGetInfoIQ mucLightGetInfoIQ = new MUCLightGetInfoIQ(room, version); 323 324 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightGetInfoIQ).nextResultOrThrow(); 325 MUCLightInfoIQ mucLightInfoResponseIQ = (MUCLightInfoIQ) responseIq; 326 327 return new MUCLightRoomInfo(mucLightInfoResponseIQ.getVersion(), room, 328 mucLightInfoResponseIQ.getConfiguration(), mucLightInfoResponseIQ.getOccupants()); 329 } 330 331 /** 332 * Get the MUC Light info. 333 * 334 * @return the room info 335 * @throws NoResponseException 336 * @throws XMPPErrorException 337 * @throws NotConnectedException 338 * @throws InterruptedException 339 */ 340 public MUCLightRoomInfo getFullInfo() 341 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 342 return getFullInfo(null); 343 } 344 345 /** 346 * Get the MUC Light configuration. 347 * 348 * @param version 349 * @return the room configuration 350 * @throws NoResponseException 351 * @throws XMPPErrorException 352 * @throws NotConnectedException 353 * @throws InterruptedException 354 */ 355 public MUCLightRoomConfiguration getConfiguration(String version) 356 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 357 MUCLightGetConfigsIQ mucLightGetConfigsIQ = new MUCLightGetConfigsIQ(room, version); 358 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightGetConfigsIQ).nextResultOrThrow(); 359 MUCLightConfigurationIQ mucLightConfigurationIQ = (MUCLightConfigurationIQ) responseIq; 360 return mucLightConfigurationIQ.getConfiguration(); 361 } 362 363 /** 364 * Get the MUC Light configuration. 365 * 366 * @return the room configuration 367 * @throws NoResponseException 368 * @throws XMPPErrorException 369 * @throws NotConnectedException 370 * @throws InterruptedException 371 */ 372 public MUCLightRoomConfiguration getConfiguration() 373 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 374 return getConfiguration(null); 375 } 376 377 /** 378 * Get the MUC Light affiliations. 379 * 380 * @param version 381 * @return the room affiliations 382 * @throws NoResponseException 383 * @throws XMPPErrorException 384 * @throws NotConnectedException 385 * @throws InterruptedException 386 */ 387 public HashMap<Jid, MUCLightAffiliation> getAffiliations(String version) 388 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 389 MUCLightGetAffiliationsIQ mucLightGetAffiliationsIQ = new MUCLightGetAffiliationsIQ(room, version); 390 391 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightGetAffiliationsIQ).nextResultOrThrow(); 392 MUCLightAffiliationsIQ mucLightAffiliationsIQ = (MUCLightAffiliationsIQ) responseIq; 393 394 return mucLightAffiliationsIQ.getAffiliations(); 395 } 396 397 /** 398 * Get the MUC Light affiliations. 399 * 400 * @return the room affiliations 401 * @throws NoResponseException 402 * @throws XMPPErrorException 403 * @throws NotConnectedException 404 * @throws InterruptedException 405 */ 406 public HashMap<Jid, MUCLightAffiliation> getAffiliations() 407 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 408 return getAffiliations(null); 409 } 410 411 /** 412 * Change the MUC Light affiliations. 413 * 414 * @param affiliations 415 * @throws NoResponseException 416 * @throws XMPPErrorException 417 * @throws NotConnectedException 418 * @throws InterruptedException 419 */ 420 public void changeAffiliations(HashMap<Jid, MUCLightAffiliation> affiliations) 421 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 422 MUCLightChangeAffiliationsIQ changeAffiliationsIQ = new MUCLightChangeAffiliationsIQ(room, affiliations); 423 connection.createStanzaCollectorAndSend(changeAffiliationsIQ).nextResultOrThrow(); 424 } 425 426 /** 427 * Destroy the MUC Light. Only will work if it is requested by the owner. 428 * 429 * @throws NoResponseException 430 * @throws XMPPErrorException 431 * @throws NotConnectedException 432 * @throws InterruptedException 433 */ 434 public void destroy() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 435 MUCLightDestroyIQ mucLightDestroyIQ = new MUCLightDestroyIQ(room); 436 IQ responseIq = connection.createStanzaCollectorAndSend(mucLightDestroyIQ).nextResultOrThrow(); 437 boolean roomDestroyed = responseIq.getType().equals(IQ.Type.result); 438 439 if (roomDestroyed) { 440 removeConnectionCallbacks(); 441 } 442 } 443 444 /** 445 * Change the subject of the MUC Light. 446 * 447 * @param subject 448 * @throws NoResponseException 449 * @throws XMPPErrorException 450 * @throws NotConnectedException 451 * @throws InterruptedException 452 */ 453 public void changeSubject(String subject) 454 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 455 MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, null, subject, null); 456 connection.createStanzaCollectorAndSend(mucLightSetConfigIQ).nextResultOrThrow(); 457 } 458 459 /** 460 * Change the name of the room. 461 * 462 * @param roomName 463 * @throws NoResponseException 464 * @throws XMPPErrorException 465 * @throws NotConnectedException 466 * @throws InterruptedException 467 */ 468 public void changeRoomName(String roomName) 469 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 470 MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, roomName, null); 471 connection.createStanzaCollectorAndSend(mucLightSetConfigIQ).nextResultOrThrow(); 472 } 473 474 /** 475 * Set the room configurations. 476 * 477 * @param customConfigs 478 * @throws NoResponseException 479 * @throws XMPPErrorException 480 * @throws NotConnectedException 481 * @throws InterruptedException 482 */ 483 public void setRoomConfigs(HashMap<String, String> customConfigs) 484 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 485 setRoomConfigs(null, customConfigs); 486 } 487 488 /** 489 * Set the room configurations. 490 * 491 * @param roomName 492 * @param customConfigs 493 * @throws NoResponseException 494 * @throws XMPPErrorException 495 * @throws NotConnectedException 496 * @throws InterruptedException 497 */ 498 public void setRoomConfigs(String roomName, HashMap<String, String> customConfigs) 499 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 500 MUCLightSetConfigsIQ mucLightSetConfigIQ = new MUCLightSetConfigsIQ(room, roomName, customConfigs); 501 connection.createStanzaCollectorAndSend(mucLightSetConfigIQ).nextResultOrThrow(); 502 } 503 504}