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