001/** 002 * 003 * Copyright © 2016-2017 Florian Schmaus, 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.mam; 018 019import java.util.ArrayList; 020import java.util.Date; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.UUID; 025import java.util.WeakHashMap; 026 027import org.jivesoftware.smack.ConnectionCreationListener; 028import org.jivesoftware.smack.Manager; 029import org.jivesoftware.smack.SmackException.NoResponseException; 030import org.jivesoftware.smack.SmackException.NotConnectedException; 031import org.jivesoftware.smack.SmackException.NotLoggedInException; 032import org.jivesoftware.smack.StanzaCollector; 033import org.jivesoftware.smack.XMPPConnection; 034import org.jivesoftware.smack.XMPPConnectionRegistry; 035import org.jivesoftware.smack.XMPPException.XMPPErrorException; 036import org.jivesoftware.smack.filter.IQReplyFilter; 037import org.jivesoftware.smack.packet.IQ; 038import org.jivesoftware.smack.packet.Message; 039import org.jivesoftware.smack.util.Objects; 040import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 041import org.jivesoftware.smackx.forward.packet.Forwarded; 042import org.jivesoftware.smackx.mam.element.MamElements; 043import org.jivesoftware.smackx.mam.element.MamFinIQ; 044import org.jivesoftware.smackx.mam.element.MamPrefsIQ; 045import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior; 046import org.jivesoftware.smackx.mam.element.MamQueryIQ; 047import org.jivesoftware.smackx.mam.filter.MamResultFilter; 048import org.jivesoftware.smackx.rsm.packet.RSMSet; 049import org.jivesoftware.smackx.xdata.FormField; 050import org.jivesoftware.smackx.xdata.packet.DataForm; 051import org.jxmpp.jid.EntityBareJid; 052import org.jxmpp.jid.EntityFullJid; 053import org.jxmpp.jid.Jid; 054import org.jxmpp.util.XmppDateTime; 055 056/** 057 * A Manager for Message Archive Management (XEP-0313). 058 * 059 * @see <a href="http://xmpp.org/extensions/xep-0313.html">XEP-0313: Message 060 * Archive Management</a> 061 * @author Florian Schmaus 062 * @author Fernando Ramirez 063 * 064 */ 065public final class MamManager extends Manager { 066 067 static { 068 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 069 @Override 070 public void connectionCreated(XMPPConnection connection) { 071 getInstanceFor(connection); 072 } 073 }); 074 } 075 076 private static final Map<XMPPConnection, Map<Jid, MamManager>> INSTANCES = new WeakHashMap<>(); 077 078 /** 079 * Get the singleton instance of MamManager. 080 * 081 * @param connection 082 * @return the instance of MamManager 083 */ 084 public static MamManager getInstanceFor(XMPPConnection connection) { 085 return getInstanceFor(connection, null); 086 } 087 088 public static synchronized MamManager getInstanceFor(XMPPConnection connection, Jid archiveAddress) { 089 Map<Jid, MamManager> managers = INSTANCES.get(connection); 090 if (managers == null) { 091 managers = new HashMap<>(); 092 INSTANCES.put(connection, managers); 093 } 094 MamManager mamManager = managers.get(archiveAddress); 095 if (mamManager == null) { 096 mamManager = new MamManager(connection, archiveAddress); 097 managers.put(archiveAddress, mamManager); 098 } 099 return mamManager; 100 } 101 102 private final Jid archiveAddress; 103 104 private MamManager(XMPPConnection connection, Jid archiveAddress) { 105 super(connection); 106 this.archiveAddress = archiveAddress; 107 } 108 109 /** 110 * Query archive with a maximum amount of results. 111 * 112 * @param max 113 * @return the MAM query result 114 * @throws NoResponseException 115 * @throws XMPPErrorException 116 * @throws NotConnectedException 117 * @throws InterruptedException 118 * @throws NotLoggedInException 119 */ 120 public MamQueryResult queryArchive(Integer max) throws NoResponseException, XMPPErrorException, 121 NotConnectedException, InterruptedException, NotLoggedInException { 122 return queryArchive(null, max, null, null, null, null); 123 } 124 125 /** 126 * Query archive with a JID (only messages from/to the JID). 127 * 128 * @param withJid 129 * @return the MAM query result 130 * @throws NoResponseException 131 * @throws XMPPErrorException 132 * @throws NotConnectedException 133 * @throws InterruptedException 134 * @throws NotLoggedInException 135 */ 136 public MamQueryResult queryArchive(Jid withJid) throws NoResponseException, XMPPErrorException, 137 NotConnectedException, InterruptedException, NotLoggedInException { 138 return queryArchive(null, null, null, null, withJid, null); 139 } 140 141 /** 142 * Query archive filtering by start and/or end date. If start == null, the 143 * value of 'start' will be equal to the date/time of the earliest message 144 * stored in the archive. If end == null, the value of 'end' will be equal 145 * to the date/time of the most recent message stored in the archive. 146 * 147 * @param start 148 * @param end 149 * @return the MAM query result 150 * @throws NoResponseException 151 * @throws XMPPErrorException 152 * @throws NotConnectedException 153 * @throws InterruptedException 154 * @throws NotLoggedInException 155 */ 156 public MamQueryResult queryArchive(Date start, Date end) throws NoResponseException, XMPPErrorException, 157 NotConnectedException, InterruptedException, NotLoggedInException { 158 return queryArchive(null, null, start, end, null, null); 159 } 160 161 /** 162 * Query Archive adding filters with additional fields. 163 * 164 * @param additionalFields 165 * @return the MAM query result 166 * @throws NoResponseException 167 * @throws XMPPErrorException 168 * @throws NotConnectedException 169 * @throws InterruptedException 170 * @throws NotLoggedInException 171 */ 172 public MamQueryResult queryArchive(List<FormField> additionalFields) throws NoResponseException, XMPPErrorException, 173 NotConnectedException, InterruptedException, NotLoggedInException { 174 return queryArchive(null, null, null, null, null, additionalFields); 175 } 176 177 /** 178 * Query archive filtering by start date. The value of 'end' will be equal 179 * to the date/time of the most recent message stored in the archive. 180 * 181 * @param start 182 * @return the MAM query result 183 * @throws NoResponseException 184 * @throws XMPPErrorException 185 * @throws NotConnectedException 186 * @throws InterruptedException 187 * @throws NotLoggedInException 188 */ 189 public MamQueryResult queryArchiveWithStartDate(Date start) throws NoResponseException, XMPPErrorException, 190 NotConnectedException, InterruptedException, NotLoggedInException { 191 return queryArchive(null, null, start, null, null, null); 192 } 193 194 /** 195 * Query archive filtering by end date. The value of 'start' will be equal 196 * to the date/time of the earliest message stored in the archive. 197 * 198 * @param end 199 * @return the MAM query result 200 * @throws NoResponseException 201 * @throws XMPPErrorException 202 * @throws NotConnectedException 203 * @throws InterruptedException 204 * @throws NotLoggedInException 205 */ 206 public MamQueryResult queryArchiveWithEndDate(Date end) throws NoResponseException, XMPPErrorException, 207 NotConnectedException, InterruptedException, NotLoggedInException { 208 return queryArchive(null, null, null, end, null, null); 209 } 210 211 212 /** 213 * Query archive applying filters: max count, start date, end date, from/to 214 * JID and with additional fields. 215 * 216 * @param max 217 * @param start 218 * @param end 219 * @param withJid 220 * @param additionalFields 221 * @return the MAM query result 222 * @throws NoResponseException 223 * @throws XMPPErrorException 224 * @throws NotConnectedException 225 * @throws InterruptedException 226 * @throws NotLoggedInException 227 */ 228 public MamQueryResult queryArchive(Integer max, Date start, Date end, Jid withJid, List<FormField> additionalFields) 229 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, 230 NotLoggedInException { 231 return queryArchive(null, max, start, end, withJid, additionalFields); 232 } 233 234 235 /** 236 * Query an message archive like a MUC archive or a pubsub node archive, addressed by an archiveAddress, applying 237 * filters: max count, start date, end date, from/to JID and with additional fields. When archiveAddress is null the 238 * default, the server will be requested. 239 * 240 * @param node The Pubsub node name, can be null 241 * @param max 242 * @param start 243 * @param end 244 * @param withJid 245 * @param additionalFields 246 * @return the MAM query result 247 * @throws NoResponseException 248 * @throws XMPPErrorException 249 * @throws NotConnectedException 250 * @throws InterruptedException 251 * @throws NotLoggedInException 252 */ 253 public MamQueryResult queryArchive(String node, Integer max, Date start, Date end, Jid withJid, 254 List<FormField> additionalFields) 255 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, 256 NotLoggedInException { 257 DataForm dataForm = null; 258 String queryId = UUID.randomUUID().toString(); 259 260 if (start != null || end != null || withJid != null || additionalFields != null) { 261 dataForm = getNewMamForm(); 262 addStart(start, dataForm); 263 addEnd(end, dataForm); 264 addWithJid(withJid, dataForm); 265 addAdditionalFields(additionalFields, dataForm); 266 } 267 268 MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, dataForm); 269 mamQueryIQ.setType(IQ.Type.set); 270 mamQueryIQ.setTo(archiveAddress); 271 272 addResultsLimit(max, mamQueryIQ); 273 return queryArchive(mamQueryIQ); 274 } 275 276 private static void addAdditionalFields(List<FormField> additionalFields, DataForm dataForm) { 277 if (additionalFields == null) { 278 return; 279 } 280 for (FormField formField : additionalFields) { 281 dataForm.addField(formField); 282 } 283 } 284 285 private static void addResultsLimit(Integer max, MamQueryIQ mamQueryIQ) { 286 if (max == null) { 287 return; 288 } 289 RSMSet rsmSet = new RSMSet(max); 290 mamQueryIQ.addExtension(rsmSet); 291 292 } 293 294 private static void addWithJid(Jid withJid, DataForm dataForm) { 295 if (withJid == null) { 296 return; 297 } 298 FormField formField = new FormField("with"); 299 formField.addValue(withJid.toString()); 300 dataForm.addField(formField); 301 } 302 303 private static void addEnd(Date end, DataForm dataForm) { 304 if (end == null) { 305 return; 306 } 307 FormField formField = new FormField("end"); 308 formField.addValue(XmppDateTime.formatXEP0082Date(end)); 309 dataForm.addField(formField); 310 } 311 312 private static void addStart(Date start, DataForm dataForm) { 313 if (start == null) { 314 return; 315 } 316 FormField formField = new FormField("start"); 317 formField.addValue(XmppDateTime.formatXEP0082Date(start)); 318 dataForm.addField(formField); 319 } 320 321 /** 322 * Returns a page of the archive. 323 * 324 * @param dataForm 325 * @param rsmSet 326 * @return the MAM query result 327 * @throws NoResponseException 328 * @throws XMPPErrorException 329 * @throws NotConnectedException 330 * @throws InterruptedException 331 * @throws NotLoggedInException 332 */ 333 public MamQueryResult page(DataForm dataForm, RSMSet rsmSet) throws NoResponseException, XMPPErrorException, 334 NotConnectedException, InterruptedException, NotLoggedInException { 335 336 return page(null, dataForm, rsmSet); 337 338 } 339 340 /** 341 * Returns a page of the archive. 342 * 343 * @param node The Pubsub node name, can be null 344 * @param dataForm 345 * @param rsmSet 346 * @return the MAM query result 347 * @throws NoResponseException 348 * @throws XMPPErrorException 349 * @throws NotConnectedException 350 * @throws InterruptedException 351 * @throws NotLoggedInException 352 */ 353 public MamQueryResult page(String node, DataForm dataForm, RSMSet rsmSet) 354 throws NoResponseException, XMPPErrorException, 355 NotConnectedException, InterruptedException, NotLoggedInException { 356 MamQueryIQ mamQueryIQ = new MamQueryIQ(UUID.randomUUID().toString(), node, dataForm); 357 mamQueryIQ.setType(IQ.Type.set); 358 mamQueryIQ.setTo(archiveAddress); 359 mamQueryIQ.addExtension(rsmSet); 360 return queryArchive(mamQueryIQ); 361 } 362 363 /** 364 * Returns the next page of the archive. 365 * 366 * @param mamQueryResult 367 * is the previous query result 368 * @param count 369 * is the amount of messages that a page contains 370 * @return the MAM query result 371 * @throws NoResponseException 372 * @throws XMPPErrorException 373 * @throws NotConnectedException 374 * @throws InterruptedException 375 * @throws NotLoggedInException 376 */ 377 public MamQueryResult pageNext(MamQueryResult mamQueryResult, int count) throws NoResponseException, 378 XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { 379 RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet(); 380 RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.after); 381 return page(mamQueryResult, requestRsmSet); 382 } 383 384 /** 385 * Returns the previous page of the archive. 386 * 387 * @param mamQueryResult 388 * is the previous query result 389 * @param count 390 * is the amount of messages that a page contains 391 * @return the MAM query result 392 * @throws NoResponseException 393 * @throws XMPPErrorException 394 * @throws NotConnectedException 395 * @throws InterruptedException 396 * @throws NotLoggedInException 397 */ 398 public MamQueryResult pagePrevious(MamQueryResult mamQueryResult, int count) throws NoResponseException, 399 XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { 400 RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet(); 401 RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getFirst(), RSMSet.PageDirection.before); 402 return page(mamQueryResult, requestRsmSet); 403 } 404 405 private MamQueryResult page(MamQueryResult mamQueryResult, RSMSet requestRsmSet) throws NoResponseException, 406 XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { 407 ensureMamQueryResultMatchesThisManager(mamQueryResult); 408 409 return page(mamQueryResult.node, mamQueryResult.form, requestRsmSet); 410 } 411 412 /** 413 * Obtain page before the first message saved (specific chat). 414 * <p> 415 * Note that the messageUid is the XEP-0313 UID and <b>not</> the stanza ID of the message. 416 * </p> 417 * 418 * @param chatJid 419 * @param messageUid the UID of the message of which messages before should be received. 420 * @param max 421 * @return the MAM query result 422 * @throws XMPPErrorException 423 * @throws NotLoggedInException 424 * @throws NotConnectedException 425 * @throws InterruptedException 426 * @throws NoResponseException 427 */ 428 public MamQueryResult pageBefore(Jid chatJid, String messageUid, int max) throws XMPPErrorException, 429 NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException { 430 RSMSet rsmSet = new RSMSet(null, messageUid, -1, -1, null, max, null, -1); 431 DataForm dataForm = getNewMamForm(); 432 addWithJid(chatJid, dataForm); 433 return page(null, dataForm, rsmSet); 434 } 435 436 /** 437 * Obtain page after the last message saved (specific chat). 438 * <p> 439 * Note that the messageUid is the XEP-0313 UID and <b>not</> the stanza ID of the message. 440 * </p> 441 * 442 * @param chatJid 443 * @param messageUid the UID of the message of which messages after should be received. 444 * @param max 445 * @return the MAM query result 446 * @throws XMPPErrorException 447 * @throws NotLoggedInException 448 * @throws NotConnectedException 449 * @throws InterruptedException 450 * @throws NoResponseException 451 */ 452 public MamQueryResult pageAfter(Jid chatJid, String messageUid, int max) throws XMPPErrorException, 453 NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException { 454 RSMSet rsmSet = new RSMSet(messageUid, null, -1, -1, null, max, null, -1); 455 DataForm dataForm = getNewMamForm(); 456 addWithJid(chatJid, dataForm); 457 return page(null, dataForm, rsmSet); 458 } 459 460 /** 461 * Obtain the most recent page of a chat. 462 * 463 * @param chatJid 464 * @param max 465 * @return the MAM query result 466 * @throws XMPPErrorException 467 * @throws NotLoggedInException 468 * @throws NotConnectedException 469 * @throws InterruptedException 470 * @throws NoResponseException 471 */ 472 public MamQueryResult mostRecentPage(Jid chatJid, int max) throws XMPPErrorException, NotLoggedInException, 473 NotConnectedException, InterruptedException, NoResponseException { 474 return pageBefore(chatJid, "", max); 475 } 476 477 /** 478 * Get the form fields supported by the server. 479 * 480 * @return the list of form fields. 481 * @throws NoResponseException 482 * @throws XMPPErrorException 483 * @throws NotConnectedException 484 * @throws InterruptedException 485 * @throws NotLoggedInException 486 */ 487 public List<FormField> retrieveFormFields() throws NoResponseException, XMPPErrorException, NotConnectedException, 488 InterruptedException, NotLoggedInException { 489 return retrieveFormFields(null); 490 } 491 492 /** 493 * Get the form fields supported by the server. 494 * 495 * @param node The Pubsub node name, can be null 496 * @return the list of form fields. 497 * @throws NoResponseException 498 * @throws XMPPErrorException 499 * @throws NotConnectedException 500 * @throws InterruptedException 501 * @throws NotLoggedInException 502 */ 503 public List<FormField> retrieveFormFields(String node) 504 throws NoResponseException, XMPPErrorException, NotConnectedException, 505 InterruptedException, NotLoggedInException { 506 String queryId = UUID.randomUUID().toString(); 507 MamQueryIQ mamQueryIq = new MamQueryIQ(queryId, node, null); 508 mamQueryIq.setTo(archiveAddress); 509 510 MamQueryIQ mamResponseQueryIq = connection().createStanzaCollectorAndSend(mamQueryIq).nextResultOrThrow(); 511 512 return mamResponseQueryIq.getDataForm().getFields(); 513 } 514 515 private MamQueryResult queryArchive(MamQueryIQ mamQueryIq) throws NoResponseException, XMPPErrorException, 516 NotConnectedException, InterruptedException, NotLoggedInException { 517 final XMPPConnection connection = getAuthenticatedConnectionOrThrow(); 518 MamFinIQ mamFinIQ = null; 519 520 StanzaCollector mamFinIQCollector = connection.createStanzaCollector(new IQReplyFilter(mamQueryIq, connection)); 521 522 StanzaCollector.Configuration resultCollectorConfiguration = StanzaCollector.newConfiguration() 523 .setStanzaFilter(new MamResultFilter(mamQueryIq)).setCollectorToReset(mamFinIQCollector); 524 StanzaCollector resultCollector = connection.createStanzaCollector(resultCollectorConfiguration); 525 526 try { 527 connection.sendStanza(mamQueryIq); 528 mamFinIQ = mamFinIQCollector.nextResultOrThrow(); 529 } finally { 530 mamFinIQCollector.cancel(); 531 resultCollector.cancel(); 532 } 533 534 List<Forwarded> forwardedMessages = new ArrayList<>(resultCollector.getCollectedCount()); 535 536 for (Message resultMessage = resultCollector 537 .pollResult(); resultMessage != null; resultMessage = resultCollector.pollResult()) { 538 MamElements.MamResultExtension mamResultExtension = MamElements.MamResultExtension.from(resultMessage); 539 forwardedMessages.add(mamResultExtension.getForwarded()); 540 } 541 542 return new MamQueryResult(forwardedMessages, mamFinIQ, mamQueryIq.getNode(), DataForm.from(mamQueryIq)); 543 } 544 545 /** 546 * MAM query result class. 547 * 548 */ 549 public final static class MamQueryResult { 550 public final List<Forwarded> forwardedMessages; 551 public final MamFinIQ mamFin; 552 private final String node; 553 private final DataForm form; 554 555 private MamQueryResult(List<Forwarded> forwardedMessages, MamFinIQ mamFin, String node, DataForm form) { 556 this.forwardedMessages = forwardedMessages; 557 this.mamFin = mamFin; 558 this.node = node; 559 this.form = form; 560 } 561 } 562 563 private void ensureMamQueryResultMatchesThisManager(MamQueryResult mamQueryResult) { 564 EntityFullJid localAddress = connection().getUser(); 565 EntityBareJid localBareAddress = null; 566 if (localAddress != null) { 567 localBareAddress = localAddress.asEntityBareJid(); 568 } 569 boolean isLocalUserArchive = archiveAddress == null || archiveAddress.equals(localBareAddress); 570 571 Jid finIqFrom = mamQueryResult.mamFin.getFrom(); 572 573 if (finIqFrom != null) { 574 if (finIqFrom.equals(archiveAddress) || (isLocalUserArchive && finIqFrom.equals(localBareAddress))) { 575 return; 576 } 577 throw new IllegalArgumentException("The given MamQueryResult is from the MAM archive '" + finIqFrom 578 + "' whereas this MamManager is responsible for '" + archiveAddress + '\''); 579 } 580 else if (!isLocalUserArchive) { 581 throw new IllegalArgumentException( 582 "The given MamQueryResult is from the local entity (user) MAM archive, whereas this MamManager is responsible for '" 583 + archiveAddress + '\''); 584 } 585 } 586 587 /** 588 * Returns true if Message Archive Management is supported by the server. 589 * 590 * @return true if Message Archive Management is supported by the server. 591 * @throws NotConnectedException 592 * @throws XMPPErrorException 593 * @throws NoResponseException 594 * @throws InterruptedException 595 */ 596 public boolean isSupportedByServer() 597 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 598 return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(MamElements.NAMESPACE); 599 } 600 601 private static DataForm getNewMamForm() { 602 FormField field = new FormField(FormField.FORM_TYPE); 603 field.setType(FormField.Type.hidden); 604 field.addValue(MamElements.NAMESPACE); 605 DataForm form = new DataForm(DataForm.Type.submit); 606 form.addField(field); 607 return form; 608 } 609 610 /** 611 * Get the preferences stored in the server. 612 * 613 * @return the MAM preferences result 614 * @throws NoResponseException 615 * @throws XMPPErrorException 616 * @throws NotConnectedException 617 * @throws InterruptedException 618 * @throws NotLoggedInException 619 */ 620 public MamPrefsResult retrieveArchivingPreferences() throws NoResponseException, XMPPErrorException, 621 NotConnectedException, InterruptedException, NotLoggedInException { 622 MamPrefsIQ mamPrefIQ = new MamPrefsIQ(); 623 return queryMamPrefs(mamPrefIQ); 624 } 625 626 /** 627 * Update the preferences in the server. 628 * 629 * @param alwaysJids 630 * is the list of JIDs that should always have messages to/from 631 * archived in the user's store 632 * @param neverJids 633 * is the list of JIDs that should never have messages to/from 634 * archived in the user's store 635 * @param defaultBehavior 636 * can be "roster", "always", "never" (see XEP-0313) 637 * @return the MAM preferences result 638 * @throws NoResponseException 639 * @throws XMPPErrorException 640 * @throws NotConnectedException 641 * @throws InterruptedException 642 * @throws NotLoggedInException 643 */ 644 public MamPrefsResult updateArchivingPreferences(List<Jid> alwaysJids, List<Jid> neverJids, DefaultBehavior defaultBehavior) 645 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, 646 NotLoggedInException { 647 Objects.requireNonNull(defaultBehavior, "Default behavior must be set"); 648 MamPrefsIQ mamPrefIQ = new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior); 649 return queryMamPrefs(mamPrefIQ); 650 } 651 652 /** 653 * MAM preferences result class. 654 * 655 */ 656 public final static class MamPrefsResult { 657 public final MamPrefsIQ mamPrefs; 658 public final DataForm form; 659 660 private MamPrefsResult(MamPrefsIQ mamPrefs, DataForm form) { 661 this.mamPrefs = mamPrefs; 662 this.form = form; 663 } 664 } 665 666 private MamPrefsResult queryMamPrefs(MamPrefsIQ mamPrefsIQ) throws NoResponseException, XMPPErrorException, 667 NotConnectedException, InterruptedException, NotLoggedInException { 668 final XMPPConnection connection = getAuthenticatedConnectionOrThrow(); 669 670 MamPrefsIQ mamPrefsResultIQ = connection.createStanzaCollectorAndSend(mamPrefsIQ).nextResultOrThrow(); 671 672 return new MamPrefsResult(mamPrefsResultIQ, DataForm.from(mamPrefsIQ)); 673 } 674 675}