1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-12-22 20:47:57 +01:00

Improve MamManager API

This commit is contained in:
Florian Schmaus 2018-06-09 15:03:14 +02:00
parent d958b42eff
commit 9161ba9e7d
8 changed files with 794 additions and 392 deletions

View file

@ -3,192 +3,4 @@ Message Archive Management
[Back](index.md)
Query and control an archive of messages stored on a server.
* Check MAM support
* Query archive
* Paging
* Get form fields
* Get preferences
* Update preferences
**XEP related:** [XEP-0313](http://xmpp.org/extensions/xep-0313.html)
Get an instance of Message Archive Management Manager
-----------------------------------------------------
```
MamManager mamManager = MamManager.getInstanceFor(connection);
```
Check MAM support
-----------------
```
boolean isSupported = mamManager.isSupported();
```
Query archive
-------------
```
MamQueryResult mamQueryResult = mamManager.queryArchive(max);
```
*max* is an `Integer`
or
```
MamQueryResult mamQueryResult = mamManager.queryArchive(withJid);
```
*withJid* is a `Jid`
or
```
MamQueryResult mamQueryResult = mamManager.queryArchive(start, end);
```
*start* is a `Date`
*end* is a `Date`
or
```
MamQueryResult mamQueryResult = mamManager.queryArchive(additionalFields);
```
*additionalFields* is a `List<FormField>`
or
```
MamQueryResult mamQueryResult = mamManager.queryArchiveWithStartDate(start);
```
*start* is a `Date`
or
```
MamQueryResult mamQueryResult = mamManager.queryArchiveWithEndDate(end);
```
*end* is a `Date`
or
```
MamQueryResult mamQueryResult = mamManager.queryArchive(max, start, end, withJid, additionalFields);
```
*max* is an `Integer`
*start* is a `Date`
*end* is a `Date`
*withJid* is a `Jid`
*additionalFields* is a `List<FormField>`
**Get data from mamQueryResult object**
```
// Get forwarded messages
List<Forwarded> forwardedMessages = mamQueryResult.forwardedMessages;
// Get fin IQ
MamFinIQ mamFinIQ = mamQueryResult.mamFin;
```
Paging
------
**Get a page**
```
MamQueryResult mamQueryResult = mamManager.page(dataForm, rsmSet);
```
*dataForm* is a `DataForm`
*rsmSet* is a `RSMSet`
**Get the next page**
```
MamQueryResult mamQueryResult = mamManager.pageNext(previousMamQueryResult, count);
```
*previousMamQueryResult* is a `MamQueryResult`
*count* is an `int`
**Get page before the first message saved (specific chat)**
```
MamQueryResult mamQueryResult = mamManager.pageBefore(chatJid, firstMessageId, max);
```
*chatJid* is a `Jid`
*firstMessageId* is a `String`
*max* is an `int`
**Get page after the last message saved (specific chat)**
```
MamQueryResult mamQueryResult = mamManager.pageAfter(chatJid, lastMessageId, max);
```
*chatJid* is a `Jid`
*lastMessageId* is a `String`
*max* is an `int`
Get form fields
---------------
```
List<FormField> formFields = mamManager.retrieveFormFields();
```
Get preferences
---------------
```
MamPrefsResult mamPrefsResult = mamManager.retrieveArchivingPreferences();
// Get preferences IQ
MamPrefsIQ mamPrefs = mamPrefsResult.mamPrefs;
// Obtain always and never list
List<Jid> alwaysJids = mamPrefs.getAlwaysJids();
List<Jid> neverJids = mamPrefs.getNeverJids();
// Obtain default behaviour (can be 'always', 'never' or 'roster')
DefaultBehavior defaultBehavior = mamPrefs.getDefault();
// Get the data form
DataForm dataForm = mamPrefsResult.form;
```
Update preferences
------------------
```
MamPrefsResult mamPrefsResult = mamManager.updateArchivingPreferences(alwaysJids, neverJids, defaultBehavior);
```
*alwaysJids* is a `List<Jid>`
*neverJids* is a `List<Jid>`
*defaultBehavior* is a `DefaultBehavior`
See the javadoc of `MamManager` for details.

View file

@ -16,7 +16,9 @@
*/
package org.jivesoftware.smackx.mam;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -36,11 +38,14 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.filter.IQReplyFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension;
import org.jivesoftware.smackx.mam.element.MamFinIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior;
@ -54,10 +59,95 @@ import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.util.XmppDateTime;
/**
* A Manager for Message Archive Management (XEP-0313).
* A Manager for Message Archive Management (MAM, <a href="http://xmpp.org/extensions/xep-0313.html">XEP-0313</a>).
*
* <h2>Get an instance of a manager for a message archive</h2>
*
* In order to work with {@link MamManager} you need to obtain an instance for a particular archive.
* To get the instance for the default archive on the user's server, use the {@link #getInstanceFor(XMPPConnection)} method.
*
* <pre>
* {@code
* XMPPConnection connection = ...
* MamManager mamManager = MamManager.getInstanceFor(connection);
* }
* </pre>
*
* If you want to retrieve a manager for a different archive use {@link #getInstanceFor(XMPPConnection, Jid)}, which takes the archive's XMPP address as second argument.
*
* <h2>Check if MAM is supported</h2>
*
* After you got your manager instance, you probably first want to check if MAM is supported.
* Simply use {@link #isSupported()} to check if there is a MAM archive available.
*
* <pre>
* {@code
* boolean isSupported = mamManager.isSupported();
* }
* </pre>
*
* <h2>Message Archive Preferences</h2>
*
* After you have verified that the MAM is supported, you probably want to configure the archive first before using it.
* One of the most important preference is to enable MAM for your account.
* Some servers set up new accounts with MAM disabled by default.
* You can do so by calling {@link #enableMamForAllMessages()}.
*
* <h3>Retrieve current preferences</h3>
*
* The archive's preferences can be retrieved using {@link #retrieveArchivingPreferences()}.
*
* <h3>Update preferences</h3>
*
* Use {@link MamPrefsResult#asMamPrefs()} to get a modifiable {@link MamPrefs} instance.
* After performing the desired changes, use {@link #updateArchivingPreferences(MamPrefs)} to update the preferences.
*
* <h2>Query the message archive</h2>
*
* Querying a message archive involves a two step process. First you need specify the query's arguments, for example a date range.
* The query arguments of a particular query are represented by a {@link MamQueryArgs} instance, which can be build using {@link MamQueryArgs.Builder}.
*
* After you have build such an instance, use {@link #queryArchive(MamQueryArgs)} to issue the query.
*
* <pre>
* {@code
* MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
* .withJid(jid)
* .setResultPageSize(10)
* .queryRecentPage()
* .build();
* MamQuery mamQuery = mamManager.queryArchive(mamQueryArgs);
* }
* </pre>
*
* On success {@link #queryArchive(MamQueryArgs)} returns a {@link MamQuery} instance.
* The instance will hold one page of the queries result set.
* Use {@link MamQuery#getMessages()} to retrieve the messages of the archive belonging to the page.
*
* You can get the whole page including all metadata using {@link MamQuery#getPage()}.
*
* <h2>Paging through the results</h2>
*
* Because the matching result set could be potentially very big, a MAM service will probably not return all matching messages.
* Instead the results are possibly send in multiple pages.
* To check if the result was complete or if there are further pages, use {@link MamQuery#isComplete()}.
* If this method returns {@code false}, then you may want to page through the archive.
*
* {@link MamQuery} provides convince methods to do so: {@link MamQuery#pageNext(int)} and {@link MamQuery#pagePrevious(int)}.
*
* <pre>
* {@code
* MamQuery nextPageMamQuery = mamQuery.pageNext(10);
* }
* </pre>
*
* <h2>Get the supported form fields</h2>
*
* You can use {@link #retrieveFormFields()} to retrieve a list of the supported additional form fields by this archive.
* Those fields can be used for further restrict a query.
*
*
* @see <a href="http://xmpp.org/extensions/xep-0313.html">XEP-0313: Message
* Archive Management</a>
@ -76,6 +166,10 @@ public final class MamManager extends Manager {
});
}
private static final String FORM_FIELD_WITH = "with";
private static final String FORM_FIELD_START = "start";
private static final String FORM_FIELD_END = "end";
private static final Map<XMPPConnection, Map<Jid, MamManager>> INSTANCES = new WeakHashMap<>();
/**
@ -144,6 +238,213 @@ public final class MamManager extends Manager {
return archiveAddress;
}
public static final class MamQueryArgs {
private final String node;
private final Map<String, FormField> formFields;
private final Integer maxResults;
private final String afterUid;
private final String beforeUid;
private MamQueryArgs(Builder builder) {
node = builder.node;
formFields = builder.formFields;
if (builder.maxResults > 0) {
maxResults = builder.maxResults;
} else {
maxResults = null;
}
afterUid = builder.afterUid;
beforeUid = builder.beforeUid;
}
private DataForm dataForm;
DataForm getDataForm() {
if (dataForm != null) {
return dataForm;
}
dataForm = getNewMamForm();
dataForm.addFields(formFields.values());
return dataForm;
}
void maybeAddRsmSet(MamQueryIQ mamQueryIQ) {
if (maxResults == null && afterUid == null && beforeUid == null) {
return;
}
int max;
if (maxResults != null) {
max = maxResults;
} else {
max = -1;
}
RSMSet rsmSet = new RSMSet(afterUid, beforeUid, -1, -1, null, max, null, -1);
mamQueryIQ.addExtension(rsmSet);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private String node;
private final Map<String, FormField> formFields = new HashMap<>(8);
private int maxResults = -1;
private String afterUid;
private String beforeUid;
public Builder queryNode(String node) {
if (node == null) {
return this;
}
this.node = node;
return this;
}
public Builder limitResultsToJid(Jid withJid) {
if (withJid == null) {
return this;
}
FormField formField = getWithFormField(withJid);
formFields.put(formField.getVariable(), formField);
return this;
}
public Builder limitResultsSince(Date start) {
if (start == null) {
return this;
}
FormField formField = new FormField(FORM_FIELD_START);
formField.addValue(start);
formFields.put(formField.getVariable(), formField);
FormField endFormField = formFields.get(FORM_FIELD_END);
if (endFormField != null) {
Date end;
try {
end = endFormField.getFirstValueAsDate();
}
catch (ParseException e) {
throw new IllegalStateException(e);
}
if (end.getTime() <= start.getTime()) {
throw new IllegalArgumentException("Given start date (" + start
+ ") is after the existing end date (" + end + ')');
}
}
return this;
}
public Builder limitResultsBefore(Date end) {
if (end == null) {
return this;
}
FormField formField = new FormField(FORM_FIELD_END);
formField.addValue(end);
formFields.put(formField.getVariable(), formField);
FormField startFormField = formFields.get(FORM_FIELD_START);
if (startFormField != null) {
Date start;
try {
start = startFormField.getFirstValueAsDate();
} catch (ParseException e) {
throw new IllegalStateException(e);
}
if (end.getTime() <= start.getTime()) {
throw new IllegalArgumentException("Given end date (" + end
+ ") is before the existing start date (" + start + ')');
}
}
return this;
}
public Builder setResultPageSize(Integer max) {
if (max == null) {
maxResults = -1;
return this;
}
return setResultPageSizeTo(max.intValue());
}
public Builder setResultPageSizeTo(int max) {
if (max < 0) {
throw new IllegalArgumentException();
}
this.maxResults = max;
return this;
}
/**
* Only return the count of messages the query yields, not the actual messages. Note that not all services
* return a correct count, some return an approximate count.
*
* @return an reference to this builder.
* @see <a href="https://xmpp.org/extensions/xep-0059.html#count">XEP-0059 § 2.7</a>
*/
public Builder onlyReturnMessageCount() {
return setResultPageSizeTo(0);
}
public Builder withAdditionalFormField(FormField formField) {
formFields.put(formField.getVariable(), formField);
return this;
}
public Builder withAdditionalFormFields(List<FormField> additionalFields) {
for (FormField formField : additionalFields) {
withAdditionalFormField(formField);
}
return this;
}
public Builder afterUid(String afterUid) {
this.afterUid = StringUtils.requireNullOrNotEmpty(afterUid, "afterUid must not be empty");
return this;
}
/**
* Specifies a message UID as 'before' anchor for the query. Note that unlike {@link #afterUid(String)} this
* method also accepts the empty String to query the last page of an archive (c.f. XEP-0059 § 2.5).
*
* @param beforeUid a message UID acting as 'before' query anchor.
* @return an instance to this builder.
*/
public Builder beforeUid(String beforeUid) {
// We don't perform any argument validation, since every possible argument (null, empty string,
// non-empty string) is valid.
this.beforeUid = beforeUid;
return this;
}
public Builder queryLastPage() {
return beforeUid("");
}
public MamQueryArgs build() {
return new MamQueryArgs(this);
}
}
}
/**
* Query archive with a maximum amount of results.
*
@ -154,7 +455,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchive(Integer max) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, max, null, null, null, null);
@ -170,7 +474,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchive(Jid withJid) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, null, null, withJid, null);
@ -190,7 +497,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchive(Date start, Date end) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, start, end, null, null);
@ -206,7 +516,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchive(List<FormField> additionalFields) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, null, null, null, additionalFields);
@ -223,7 +536,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchiveWithStartDate(Date start) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, start, null, null, null);
@ -240,7 +556,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchiveWithEndDate(Date end) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, null, end, null, null);
@ -262,7 +581,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchive(Integer max, Date start, Date end, Jid withJid, List<FormField> additionalFields)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException,
NotLoggedInException {
@ -287,72 +609,53 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult queryArchive(String node, Integer max, Date start, Date end, Jid withJid,
List<FormField> additionalFields)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException,
NotLoggedInException {
DataForm dataForm = null;
String queryId = UUID.randomUUID().toString();
MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
.queryNode(node)
.setResultPageSize(max)
.limitResultsSince(start)
.limitResultsBefore(end)
.limitResultsToJid(withJid)
.withAdditionalFormFields(additionalFields)
.build();
if (start != null || end != null || withJid != null || additionalFields != null) {
dataForm = getNewMamForm();
addStart(start, dataForm);
addEnd(end, dataForm);
addWithJid(withJid, dataForm);
addAdditionalFields(additionalFields, dataForm);
}
MamQuery mamQuery = queryArchive(mamQueryArgs);
return new MamQueryResult(mamQuery);
}
public MamQuery queryArchive(MamQueryArgs mamQueryArgs) throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
String queryId = UUID.randomUUID().toString();
String node = mamQueryArgs.node;
DataForm dataForm = mamQueryArgs.getDataForm();
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
addResultsLimit(max, mamQueryIQ);
mamQueryArgs.maybeAddRsmSet(mamQueryIQ);
return queryArchive(mamQueryIQ);
}
private static void addAdditionalFields(List<FormField> additionalFields, DataForm dataForm) {
if (additionalFields == null) {
return;
}
for (FormField formField : additionalFields) {
dataForm.addField(formField);
}
}
private static void addResultsLimit(Integer max, MamQueryIQ mamQueryIQ) {
if (max == null) {
return;
}
RSMSet rsmSet = new RSMSet(max);
mamQueryIQ.addExtension(rsmSet);
private static FormField getWithFormField(Jid withJid) {
FormField formField = new FormField(FORM_FIELD_WITH);
formField.addValue(withJid.toString());
return formField;
}
private static void addWithJid(Jid withJid, DataForm dataForm) {
if (withJid == null) {
return;
}
FormField formField = new FormField("with");
formField.addValue(withJid.toString());
dataForm.addField(formField);
}
private static void addEnd(Date end, DataForm dataForm) {
if (end == null) {
return;
}
FormField formField = new FormField("end");
formField.addValue(XmppDateTime.formatXEP0082Date(end));
dataForm.addField(formField);
}
private static void addStart(Date start, DataForm dataForm) {
if (start == null) {
return;
}
FormField formField = new FormField("start");
formField.addValue(XmppDateTime.formatXEP0082Date(start));
FormField formField = getWithFormField(withJid);
dataForm.addField(formField);
}
@ -367,16 +670,18 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult page(DataForm dataForm, RSMSet rsmSet) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return page(null, dataForm, rsmSet);
}
/**
* Returns a page of the archive.
* Returns a page of the archive. This is a low-level method, you possibly do not want to use it directly unless you
* know what you are doing.
*
* @param node The PubSub node name, can be null
* @param dataForm
@ -387,7 +692,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult page(String node, DataForm dataForm, RSMSet rsmSet)
throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
@ -395,7 +703,8 @@ public final class MamManager extends Manager {
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
mamQueryIQ.addExtension(rsmSet);
return queryArchive(mamQueryIQ);
MamQuery mamQuery = queryArchive(mamQueryIQ);
return new MamQueryResult(mamQuery);
}
/**
@ -411,7 +720,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link MamQuery#pageNext(int)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult pageNext(MamQueryResult mamQueryResult, int count) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException {
RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet();
@ -432,7 +744,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link MamQuery#pagePrevious(int)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult pagePrevious(MamQueryResult mamQueryResult, int count) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException {
RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet();
@ -462,7 +777,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NoResponseException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult pageBefore(Jid chatJid, String messageUid, int max) throws XMPPErrorException,
NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException {
RSMSet rsmSet = new RSMSet(null, messageUid, -1, -1, null, max, null, -1);
@ -486,7 +804,10 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NoResponseException
* @deprecated use {@link #queryArchive(MamQueryArgs)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult pageAfter(Jid chatJid, String messageUid, int max) throws XMPPErrorException,
NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException {
RSMSet rsmSet = new RSMSet(messageUid, null, -1, -1, null, max, null, -1);
@ -506,12 +827,26 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NoResponseException
* @deprecated use {@link #queryMostRecentPage(Jid, int)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MamQueryResult mostRecentPage(Jid chatJid, int max) throws XMPPErrorException, NotLoggedInException,
NotConnectedException, InterruptedException, NoResponseException {
return pageBefore(chatJid, "", max);
}
public MamQuery queryMostRecentPage(Jid jid, int max) throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
// Produces an empty <before/> element for XEP-0059 § 2.5
.queryLastPage()
.limitResultsToJid(jid)
.setResultPageSize(max)
.build();
return queryArchive(mamQueryArgs);
}
/**
* Get the form fields supported by the server.
*
@ -550,8 +885,14 @@ public final class MamManager extends Manager {
return mamResponseQueryIq.getDataForm().getFields();
}
private MamQueryResult queryArchive(MamQueryIQ mamQueryIq) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
private MamQuery queryArchive(MamQueryIQ mamQueryIq) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
MamQueryPage mamQueryPage = queryArchivePage(mamQueryIq);
return new MamQuery(mamQueryPage, mamQueryIq.getNode(), DataForm.from(mamQueryIq));
}
private MamQueryPage queryArchivePage(MamQueryIQ mamQueryIq) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
final XMPPConnection connection = getAuthenticatedConnectionOrThrow();
MamFinIQ mamFinIQ;
@ -569,27 +910,24 @@ public final class MamManager extends Manager {
resultCollector.cancel();
}
List<Forwarded> forwardedMessages = new ArrayList<>(resultCollector.getCollectedCount());
for (Message resultMessage = resultCollector
.pollResult(); resultMessage != null; resultMessage = resultCollector.pollResult()) {
MamElements.MamResultExtension mamResultExtension = MamElements.MamResultExtension.from(resultMessage);
forwardedMessages.add(mamResultExtension.getForwarded());
}
return new MamQueryResult(forwardedMessages, mamFinIQ, mamQueryIq.getNode(), DataForm.from(mamQueryIq));
return new MamQueryPage(resultCollector, mamFinIQ);
}
/**
* MAM query result class.
*
*/
@Deprecated
public static final class MamQueryResult {
public final List<Forwarded> forwardedMessages;
public final MamFinIQ mamFin;
private final String node;
private final DataForm form;
private MamQueryResult(MamQuery mamQuery) {
this(mamQuery.mamQueryPage.forwardedMessages, mamQuery.mamQueryPage.mamFin, mamQuery.node, mamQuery.form);
}
private MamQueryResult(List<Forwarded> forwardedMessages, MamFinIQ mamFin, String node, DataForm form) {
this.forwardedMessages = forwardedMessages;
this.mamFin = mamFin;
@ -598,6 +936,124 @@ public final class MamManager extends Manager {
}
}
public final class MamQuery {
private final String node;
private final DataForm form;
private MamQueryPage mamQueryPage;
private MamQuery(MamQueryPage mamQueryPage, String node, DataForm form) {
this.node = node;
this.form = form;
this.mamQueryPage = mamQueryPage;
}
public boolean isComplete() {
return mamQueryPage.getMamFinIq().isComplete();
}
public List<Message> getMessages() {
return mamQueryPage.messages;
}
public List<MamResultExtension> getMamResultExtensions() {
return mamQueryPage.mamResultExtensions;
}
private List<Message> page(RSMSet requestRsmSet) throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
MamQueryIQ mamQueryIQ = new MamQueryIQ(UUID.randomUUID().toString(), node, form);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
mamQueryIQ.addExtension(requestRsmSet);
mamQueryPage = queryArchivePage(mamQueryIQ);
return mamQueryPage.messages;
}
private RSMSet getPreviousRsmSet() {
return mamQueryPage.getMamFinIq().getRSMSet();
}
public List<Message> pageNext(int count) throws NoResponseException, XMPPErrorException, NotConnectedException,
NotLoggedInException, InterruptedException {
RSMSet previousResultRsmSet = getPreviousRsmSet();
RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.after);
return page(requestRsmSet);
}
public List<Message> pagePrevious(int count) throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
RSMSet previousResultRsmSet = getPreviousRsmSet();
RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.before);
return page(requestRsmSet);
}
public int getMessageCount() {
return getMessages().size();
}
public MamQueryPage getPage() {
return mamQueryPage;
}
}
public static final class MamQueryPage {
private final MamFinIQ mamFin;
private final List<Message> mamResultCarrierMessages;
private final List<MamResultExtension> mamResultExtensions;
private final List<Forwarded> forwardedMessages;
private final List<Message> messages;
private MamQueryPage(StanzaCollector stanzaCollector, MamFinIQ mamFin) {
this.mamFin = mamFin;
List<Stanza> mamResultCarrierStanzas = stanzaCollector.getCollectedStanzasAfterCancelled();
List<Message> mamResultCarrierMessages = new ArrayList<>(mamResultCarrierStanzas.size());
List<MamResultExtension> mamResultExtensions = new ArrayList<>(mamResultCarrierStanzas.size());
List<Forwarded> forwardedMessages = new ArrayList<>(mamResultCarrierStanzas.size());
for (Stanza mamResultStanza : mamResultCarrierStanzas) {
Message resultMessage = (Message) mamResultStanza;
mamResultCarrierMessages.add(resultMessage);
MamElements.MamResultExtension mamResultExtension = MamElements.MamResultExtension.from(resultMessage);
mamResultExtensions.add(mamResultExtension);
forwardedMessages.add(mamResultExtension.getForwarded());
}
this.mamResultCarrierMessages = Collections.unmodifiableList(mamResultCarrierMessages);
this.mamResultExtensions = Collections.unmodifiableList(mamResultExtensions);
this.forwardedMessages = Collections.unmodifiableList(forwardedMessages);
this.messages = Collections.unmodifiableList(Forwarded.extractMessagesFrom(forwardedMessages));
}
public List<Message> getMessages() {
return messages;
}
public List<Forwarded> getForwarded() {
return forwardedMessages;
}
public List<MamResultExtension> getMamResultExtensions() {
return mamResultExtensions;
}
public List<Message> getMamResultCarrierMessages() {
return mamResultCarrierMessages;
}
public MamFinIQ getMamFinIq() {
return mamFin;
}
}
private void ensureMamQueryResultMatchesThisManager(MamQueryResult mamQueryResult) {
EntityFullJid localAddress = connection().getUser();
EntityBareJid localBareAddress = null;
@ -649,6 +1105,32 @@ public final class MamManager extends Manager {
return form;
}
/**
* Lookup the archive's message ID of the latest message in the archive. Returns {@code null} if the archive is
* empty.
*
* @return the ID of the lastest message or {@code null}.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws NotLoggedInException
* @throws InterruptedException
* @since 4.3.0
*/
public String getMessageUidOfLatestMessage() throws NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException {
MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
.setResultPageSize(1)
.queryLastPage()
.build();
MamQuery mamQuery = queryArchive(mamQueryArgs);
if (mamQuery.getMessages().isEmpty()) {
return null;
}
return mamQuery.getMamResultExtensions().get(0).getId();
}
/**
* Get the preferences stored in the server.
*
@ -682,7 +1164,9 @@ public final class MamManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @deprecated use {@link #updateArchivingPreferences(MamPrefs)} instead.
*/
@Deprecated
public MamPrefsResult updateArchivingPreferences(List<Jid> alwaysJids, List<Jid> neverJids, DefaultBehavior defaultBehavior)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException,
NotLoggedInException {
@ -691,6 +1175,46 @@ public final class MamManager extends Manager {
return queryMamPrefs(mamPrefIQ);
}
/**
* Update the preferences in the server.
*
* @param mamPrefs
* @return the currently active preferences after the operation.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
* @since 4.3.0
*/
public MamPrefsResult updateArchivingPreferences(MamPrefs mamPrefs) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
MamPrefsIQ mamPrefIQ = mamPrefs.constructMamPrefsIq();
return queryMamPrefs(mamPrefIQ);
}
public MamPrefsResult enableMamForAllMessages() throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
return setDefaultBehavior(DefaultBehavior.always);
}
public MamPrefsResult enableMamForRosterMessages() throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
return setDefaultBehavior(DefaultBehavior.roster);
}
public MamPrefsResult setDefaultBehavior(DefaultBehavior desiredDefaultBehavior) throws NoResponseException,
XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException {
MamPrefsResult mamPrefsResult = retrieveArchivingPreferences();
if (mamPrefsResult.mamPrefs.getDefault() == desiredDefaultBehavior) {
return mamPrefsResult;
}
MamPrefs mamPrefs = mamPrefsResult.asMamPrefs();
mamPrefs.setDefaultBehavior(desiredDefaultBehavior);
return updateArchivingPreferences(mamPrefs);
}
/**
* MAM preferences result class.
*
@ -703,6 +1227,43 @@ public final class MamManager extends Manager {
this.mamPrefs = mamPrefs;
this.form = form;
}
public MamPrefs asMamPrefs() {
return new MamPrefs(this);
}
}
public static final class MamPrefs {
private final List<Jid> alwaysJids;
private final List<Jid> neverJids;
private DefaultBehavior defaultBehavior;
private MamPrefs(MamPrefsResult mamPrefsResult) {
MamPrefsIQ mamPrefsIq = mamPrefsResult.mamPrefs;
this.alwaysJids = new ArrayList<>(mamPrefsIq.getAlwaysJids());
this.neverJids = new ArrayList<>(mamPrefsIq.getNeverJids());
this.defaultBehavior = mamPrefsIq.getDefault();
}
public void setDefaultBehavior(DefaultBehavior defaultBehavior) {
this.defaultBehavior = Objects.requireNonNull(defaultBehavior, "defaultBehavior must not be null");
}
public DefaultBehavior getDefaultBehavior() {
return defaultBehavior;
}
public List<Jid> getAlwaysJids() {
return alwaysJids;
}
public List<Jid> getNeverJids() {
return neverJids;
}
private MamPrefsIQ constructMamPrefsIq() {
return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior);
}
}
private MamPrefsResult queryMamPrefs(MamPrefsIQ mamPrefsIQ) throws NoResponseException, XMPPErrorException,

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016 Fernando Ramirez
* Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,23 +16,24 @@
*/
package org.jivesoftware.smackx.mam;
import java.lang.reflect.Method;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.Assert;
import org.junit.Test;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.JidTestUtil;
import org.jxmpp.util.XmppDateTime;
public class FiltersTest extends MamTest {
private static String getMamXMemberWith(List<String> fieldsNames, List<String> fieldsValues) {
private static String getMamXMemberWith(List<String> fieldsNames, List<? extends CharSequence> fieldsValues) {
String xml = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE' type='hidden'>" + "<value>"
+ MamElements.NAMESPACE + "</value>" + "</field>";
@ -47,115 +48,47 @@ public class FiltersTest extends MamTest {
@Test
public void checkStartDateFilter() throws Exception {
Method methodAddStartDateFilter = MamManager.class.getDeclaredMethod("addStart", Date.class, DataForm.class);
methodAddStartDateFilter.setAccessible(true);
Date date = new Date();
DataForm dataForm = getNewMamForm();
methodAddStartDateFilter.invoke(mamManager, date, dataForm);
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsSince(date).build();
DataForm dataForm = mamQueryArgs.getDataForm();
List<String> fields = new ArrayList<>();
fields.add("start");
List<String> values = new ArrayList<>();
values.add(XmppDateTime.formatXEP0082Date(date));
Assert.assertEquals(dataForm.toXML(null).toString(), getMamXMemberWith(fields, values));
assertEquals(getMamXMemberWith(fields, values), dataForm.toXML(null).toString());
}
@Test
public void checkEndDateFilter() throws Exception {
Method methodAddEndDateFilter = MamManager.class.getDeclaredMethod("addEnd", Date.class, DataForm.class);
methodAddEndDateFilter.setAccessible(true);
Date date = new Date();
DataForm dataForm = getNewMamForm();
methodAddEndDateFilter.invoke(mamManager, date, dataForm);
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsBefore(date).build();
DataForm dataForm = mamQueryArgs.getDataForm();
List<String> fields = new ArrayList<>();
fields.add("end");
List<String> values = new ArrayList<>();
values.add(XmppDateTime.formatXEP0082Date(date));
Assert.assertEquals(dataForm.toXML(null).toString(), getMamXMemberWith(fields, values));
assertEquals(getMamXMemberWith(fields, values), dataForm.toXML(null).toString());
}
@Test
public void checkWithJidFilter() throws Exception {
Method methodAddJidFilter = MamManager.class.getDeclaredMethod("addWithJid", Jid.class, DataForm.class);
methodAddJidFilter.setAccessible(true);
Jid jid = JidTestUtil.BARE_JID_1;
String jid = "test@jid.com";
DataForm dataForm = getNewMamForm();
methodAddJidFilter.invoke(mamManager, JidCreate.from(jid), dataForm);
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsToJid(jid).build();
DataForm dataForm = mamQueryArgs.getDataForm();
List<String> fields = new ArrayList<>();
fields.add("with");
List<String> values = new ArrayList<>();
List<CharSequence> values = new ArrayList<>();
values.add(jid);
Assert.assertEquals(dataForm.toXML(null).toString(), getMamXMemberWith(fields, values));
}
@Test
public void checkMultipleFilters() throws Exception {
Method methodAddStartDateFilter = MamManager.class.getDeclaredMethod("addStart", Date.class, DataForm.class);
methodAddStartDateFilter.setAccessible(true);
Method methodAddEndDateFilter = MamManager.class.getDeclaredMethod("addEnd", Date.class, DataForm.class);
methodAddEndDateFilter.setAccessible(true);
Method methodAddJidFilter = MamManager.class.getDeclaredMethod("addWithJid", Jid.class, DataForm.class);
methodAddJidFilter.setAccessible(true);
DataForm dataForm = getNewMamForm();
Date date = new Date();
String dateString = XmppDateTime.formatXEP0082Date(date);
String jid = "test@jid.com";
methodAddStartDateFilter.invoke(mamManager, date, dataForm);
methodAddEndDateFilter.invoke(mamManager, date, dataForm);
methodAddJidFilter.invoke(mamManager, JidCreate.from(jid), dataForm);
String dataFormResult = dataForm.toXML(null).toString();
List<String> fields = new ArrayList<>();
List<String> values = new ArrayList<>();
fields.add("start");
values.add(dateString);
Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values));
fields.add("end");
values.add(dateString);
Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values));
fields.clear();
values.clear();
fields.add("start");
values.add(dateString);
fields.add("with");
values.add(jid);
Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values));
fields.clear();
values.clear();
fields.add("end");
values.add(dateString);
fields.add("with");
values.add(jid);
fields.add("start");
values.add(dateString);
Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values));
fields.clear();
values.clear();
fields.add("start");
values.add(dateString);
fields.add("end");
values.add(dateString);
fields.add("with");
values.add(jid);
Assert.assertEquals(dataFormResult, getMamXMemberWith(fields, values));
assertEquals(getMamXMemberWith(fields, values), dataForm.toXML(null).toString());
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016 Fernando Ramirez
* Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,10 @@
*/
package org.jivesoftware.smackx.mam;
import java.lang.reflect.Method;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -37,16 +36,14 @@ public class ResultsLimitTest extends MamTest {
@Test
public void checkResultsLimit() throws Exception {
Method methodAddResultsLimit = MamManager.class.getDeclaredMethod("addResultsLimit", Integer.class,
MamQueryIQ.class);
methodAddResultsLimit.setAccessible(true);
DataForm dataForm = getNewMamForm();
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setStanzaId("sarasa");
methodAddResultsLimit.invoke(mamManager, 10, mamQueryIQ);
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().setResultPageSize(10).build();
mamQueryArgs.maybeAddRsmSet(mamQueryIQ);
Assert.assertEquals(mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), resultsLimitStanza);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016 Fernando Ramirez
* Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,18 +16,17 @@
*/
package org.jivesoftware.smackx.mam;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.Assert;
import org.junit.Test;
public class RetrieveFormFieldsTest extends MamTest {
@ -46,19 +45,11 @@ public class RetrieveFormFieldsTest extends MamTest {
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId);
mamQueryIQ.setStanzaId("sarasa");
Assert.assertEquals(mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), retrieveFormFieldStanza);
assertEquals(retrieveFormFieldStanza, mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@Test
public void checkAddAdditionalFieldsStanza() throws Exception {
Method methodAddAdditionalFields = MamManager.class.getDeclaredMethod("addAdditionalFields", List.class,
DataForm.class);
methodAddAdditionalFields.setAccessible(true);
DataForm dataForm = getNewMamForm();
List<FormField> additionalFields = new ArrayList<>();
FormField field1 = new FormField("urn:example:xmpp:free-text-search");
field1.setType(FormField.Type.text_single);
field1.addValue("Hi");
@ -67,14 +58,15 @@ public class RetrieveFormFieldsTest extends MamTest {
field2.setType(FormField.Type.jid_single);
field2.addValue("Hi2");
additionalFields.add(field1);
additionalFields.add(field2);
methodAddAdditionalFields.invoke(mamManager, additionalFields, dataForm);
MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
.withAdditionalFormField(field1)
.withAdditionalFormField(field2)
.build();
DataForm dataForm = mamQueryArgs.getDataForm();
String dataFormResult = dataForm.toXML(null).toString();
Assert.assertEquals(dataFormResult, additionalFieldsStanza);
assertXmlSimilar(additionalFieldsStanza, dataFormResult);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016 Fernando Ramirez
* Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,23 +17,30 @@
package org.jivesoftware.smackx.mam;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.filter.MessageWithBodiesFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.mam.MamManager.MamQueryResult;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.MamManager.MamQuery;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.jxmpp.jid.EntityBareJid;
public class MamIntegrationTest extends AbstractSmackIntegrationTest {
@ -41,7 +48,7 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest {
private final MamManager mamManagerConTwo;
public MamIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException {
XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException, NotLoggedInException {
super(environment);
mamManagerConTwo = MamManager.getInstanceFor(conTwo);
@ -50,33 +57,51 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest {
throw new TestNotPossibleException("Message Archive Management (XEP-0313) is not supported by the server.");
}
// Make sure MAM is archiving messages.
mamManagerConTwo.enableMamForAllMessages();
}
@SmackIntegrationTest
public void mamTest() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException,
NotLoggedInException {
public void mamTest() throws TimeoutException, Exception {
EntityBareJid userOne = conOne.getUser().asEntityBareJid();
EntityBareJid userTwo = conTwo.getUser().asEntityBareJid();
// Make sure MAM is archiving messages.
mamManagerConTwo.updateArchivingPreferences(null, null, MamPrefsIQ.DefaultBehavior.always);
Message message = new Message(userTwo);
String messageId = message.setStanzaId();
String messageBody = "test message";
final String messageBody = "Test MAM message (" + testRunId + ')';
message.setBody(messageBody);
conOne.sendStanza(message);
final SimpleResultSyncPoint messageReceived = new SimpleResultSyncPoint();
int pageSize = 20;
MamQueryResult mamQueryResult = mamManagerConTwo.queryArchive(pageSize, null, null, userOne, null);
final StanzaListener stanzaListener = new StanzaListener() {
@Override
public void processStanza(Stanza stanza) {
Message message = (Message) stanza;
if (message.getBody().equals(messageBody)) {
messageReceived.signal();
}
}
};
conTwo.addAsyncStanzaListener(stanzaListener, MessageWithBodiesFilter.INSTANCE);
while (!mamQueryResult.mamFin.isComplete()) {
mamQueryResult = mamManagerConTwo.pageNext(mamQueryResult, pageSize);
try {
conOne.sendStanza(message);
messageReceived.waitForResult(timeout);
} finally {
conTwo.removeAsyncStanzaListener(stanzaListener);
}
List<Forwarded> forwardedMessages = mamQueryResult.forwardedMessages;
Message mamMessage = (Message) forwardedMessages.get(forwardedMessages.size() - 1).getForwardedStanza();
MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
.setResultPageSizeTo(1)
.limitResultsToJid(userOne)
.queryLastPage()
.build();
MamQuery mamQuery = mamManagerConTwo.queryArchive(mamQueryArgs);
assertEquals(1, mamQuery.getMessages().size());
Message mamMessage = mamQuery.getMessages().get(0);
assertEquals(messageId, mamMessage.getStanzaId());
assertEquals(messageBody, mamMessage.getBody());
@ -84,4 +109,87 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest {
assertEquals(userTwo, mamMessage.getTo());
}
@SmackIntegrationTest
public void mamPageTest() throws TimeoutException, Exception {
final int messagesPerPage = 10;
final int numPages = 3;
final int totalMessages = messagesPerPage * numPages;
final List<Message> outgoingMessages = new ArrayList<>(totalMessages);
final EntityBareJid userOne = conOne.getUser().asEntityBareJid();
final EntityBareJid userTwo = conTwo.getUser().asEntityBareJid();
final SimpleResultSyncPoint allMessagesReceived = new SimpleResultSyncPoint();
final String lastMessageArchiveUid = mamManagerConTwo.getMessageUidOfLatestMessage();
for (int i = 0; i < totalMessages; i++) {
String messageBody = "MAM Page Test " + testRunId + ' ' + (i + 1);
Message message = new Message(userTwo, messageBody);
outgoingMessages.add(message);
}
final String lastBody = outgoingMessages.get(outgoingMessages.size() - 1).getBody();
final StanzaListener stanzaListener = new StanzaListener() {
@Override
public void processStanza(Stanza stanza) {
Message message = (Message) stanza;
if (message.getBody().equals(lastBody)) {
allMessagesReceived.signal();
}
}
};
conTwo.addAsyncStanzaListener(stanzaListener, MessageWithBodiesFilter.INSTANCE);
try {
for (Message message : outgoingMessages) {
conOne.sendStanza(message);
}
allMessagesReceived.waitForResult(timeout);
} finally {
conTwo.removeAsyncStanzaListener(stanzaListener);
}
MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
.setResultPageSize(messagesPerPage)
.limitResultsToJid(userOne)
.afterUid(lastMessageArchiveUid)
.build();
MamQuery mamQuery = mamManagerConTwo.queryArchive(mamQueryArgs);
assertFalse(mamQuery.isComplete());
assertEquals(messagesPerPage, mamQuery.getMessageCount());
List<List<Message>> pages = new ArrayList<>(numPages);
pages.add(mamQuery.getMessages());
for (int additionalPageRequestNum = 0; additionalPageRequestNum < numPages - 1; additionalPageRequestNum++) {
List<Message> page = mamQuery.pageNext(messagesPerPage);
boolean isLastQuery = additionalPageRequestNum == numPages - 2;
if (isLastQuery) {
assertTrue(mamQuery.isComplete());
} else {
assertFalse(mamQuery.isComplete());
}
assertEquals(messagesPerPage, page.size());
pages.add(page);
}
List<Message> queriedMessages = new ArrayList<>(totalMessages);
for (List<Message> messages : pages) {
queriedMessages.addAll(messages);
}
assertEquals(outgoingMessages.size(), queriedMessages.size());
for (int i = 0; i < outgoingMessages.size(); i++) {
Message outgoingMessage = outgoingMessages.get(i);
Message queriedMessage = queriedMessages.get(i);
assertEquals(outgoingMessage.getBody(), queriedMessage.getBody());
}
}
}

View file

@ -325,16 +325,16 @@ public final class OmemoManager extends Manager {
* Return a list of all OMEMO messages that were found in the MAM query result, that could be successfully decrypted.
* Normal cleartext messages are also added to this list.
*
* @param mamQueryResult mamQueryResult
* @param mamQuery The MAM query
* @return list of decrypted OmemoMessages
* @throws InterruptedException Exception
* @throws XMPPException.XMPPErrorException Exception
* @throws SmackException.NotConnectedException Exception
* @throws SmackException.NoResponseException Exception
*/
public List<ClearTextMessage> decryptMamQueryResult(MamManager.MamQueryResult mamQueryResult) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
public List<ClearTextMessage> decryptMamQueryResult(MamManager.MamQuery mamQuery) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
List<ClearTextMessage> l = new ArrayList<>();
l.addAll(getOmemoService().decryptMamQueryResult(this, mamQueryResult));
l.addAll(getOmemoService().decryptMamQueryResult(this, mamQuery));
return l;
}

View file

@ -54,7 +54,6 @@ import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
@ -1127,21 +1126,21 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
List<ClearTextMessage> decryptMamQueryResult(OmemoManager omemoManager, MamManager.MamQueryResult mamQueryResult)
List<ClearTextMessage> decryptMamQueryResult(OmemoManager omemoManager, MamManager.MamQuery mamQuery)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
List<ClearTextMessage> result = new ArrayList<>();
for (Forwarded f : mamQueryResult.forwardedMessages) {
if (OmemoManager.stanzaContainsOmemoElement(f.getForwardedStanza())) {
for (Message message : mamQuery.getMessages()) {
if (OmemoManager.stanzaContainsOmemoElement(message)) {
// Decrypt OMEMO messages
try {
result.add(processLocalMessage(omemoManager, f.getForwardedStanza().getFrom().asBareJid(), (Message) f.getForwardedStanza()));
result.add(processLocalMessage(omemoManager, message.getFrom().asBareJid(), message));
} catch (NoRawSessionException | CorruptedOmemoKeyException | CryptoFailedException e) {
LOGGER.log(Level.WARNING, "decryptMamQueryResult failed to decrypt message from "
+ f.getForwardedStanza().getFrom() + " due to corrupted session/key: " + e.getMessage());
+ message.getFrom() + " due to corrupted session/key: " + e.getMessage());
}
} else {
// Wrap cleartext messages
Message m = (Message) f.getForwardedStanza();
Message m = message;
result.add(new ClearTextMessage(m.getBody(), m,
new OmemoMessageInformation(null, null, OmemoMessageInformation.CARBON.NONE, false)));
}