Merge branch '4.3'

This commit is contained in:
Florian Schmaus 2018-06-12 15:49:43 +02:00
commit f290197f6a
20 changed files with 1149 additions and 424 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

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2016-2017 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2016-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,6 +17,8 @@
package org.jivesoftware.smack;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
@ -55,7 +57,7 @@ public class StanzaCollector {
private final Stanza request;
private boolean cancelled = false;
private volatile boolean cancelled = false;
/**
* Creates a new stanza collector. If the stanza filter is <tt>null</tt>, then
@ -268,6 +270,27 @@ public class StanzaCollector {
return result;
}
private List<Stanza> collectedCache;
/**
* Return a list of all collected stanzas. This method must be invoked after the collector has been cancelled.
*
* @return a list of collected stanzas.
* @since 4.3.0
*/
public List<Stanza> getCollectedStanzasAfterCancelled() {
if (!cancelled) {
throw new IllegalStateException("Stanza collector was not yet cancelled");
}
if (collectedCache == null) {
collectedCache = new ArrayList<>(getCollectedCount());
resultQueue.drainTo(collectedCache);
}
return collectedCache;
}
/**
* Get the number of collected stanzas this stanza collector has collected so far.
*
@ -300,7 +323,7 @@ public class StanzaCollector {
private void throwIfCancelled() {
if (cancelled) {
throw new IllegalStateException("Packet collector already cancelled");
throw new IllegalStateException("Stanza collector already cancelled");
}
}

View File

@ -0,0 +1,94 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
/**
* Extends a {@link HashMap} with {@link WeakReference} values, so that
* weak references which have been cleared are periodically removed from
* the map. The cleaning occurs as part of {@link #put}, after a specific
* number ({@link #cleanInterval}) of calls to {@link #put}.
*
* @param <K> The key type.
* @param <V> The value type.
*
* @author Boris Grozev
*/
public class CleaningWeakReferenceMap<K, V>
extends HashMap<K, WeakReference<V>> {
private static final long serialVersionUID = 0L;
/**
* The number of calls to {@link #put} after which to clean this map
* (i.e. remove cleared {@link WeakReference}s from it).
*/
private final int cleanInterval;
/**
* The number of times {@link #put} has been called on this instance
* since the last time it was {@link #clean}ed.
*/
private int numberOfInsertsSinceLastClean = 0;
/**
* Initializes a new {@link CleaningWeakReferenceMap} instance with the
* default clean interval.
*/
public CleaningWeakReferenceMap() {
this(50);
}
/**
* Initializes a new {@link CleaningWeakReferenceMap} instance with a given
* clean interval.
* @param cleanInterval the number of calls to {@link #put} after which the
* map will clean itself.
*/
public CleaningWeakReferenceMap(int cleanInterval) {
this.cleanInterval = cleanInterval;
}
@Override
public WeakReference<V> put(K key, WeakReference<V> value) {
WeakReference<V> ret = super.put(key, value);
if (numberOfInsertsSinceLastClean++ > cleanInterval) {
numberOfInsertsSinceLastClean = 0;
clean();
}
return ret;
}
/**
* Removes all cleared entries from this map (i.e. entries whose value
* is a cleared {@link WeakReference}).
*/
private void clean() {
Iterator<Entry<K, WeakReference<V>>> iter = entrySet().iterator();
while (iter.hasNext()) {
Entry<K, WeakReference<V>> e = iter.next();
if (e != null && e.getValue() != null
&& e.getValue().get() == null) {
iter.remove();
}
}
}
}

View File

@ -451,6 +451,16 @@ public class StringUtils {
return cs;
}
public static <CS extends CharSequence> CS requireNullOrNotEmpty(CS cs, String message) {
if (cs == null) {
return null;
}
if (cs.toString().isEmpty()) {
throw new IllegalArgumentException(message);
}
return cs;
}
/**
* Return the String representation of the given char sequence if it is not null.
*

View File

@ -75,7 +75,9 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
* @return the XmlStringBuilder
*/
public XmlStringBuilder element(String name, String content) {
assert content != null;
if (content.isEmpty()) {
return emptyElement(name);
}
openElement(name);
escape(content);
closeElement(name);

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2014 Florian Schmaus
* Copyright 2014-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.
@ -26,10 +26,14 @@ import org.xml.sax.SAXException;
public class XmlUnitUtils {
// TOOD: Remove this method.
public static void assertSimilar(CharSequence expected, CharSequence actual) throws SAXException, IOException {
assertXmlSimilar(expected, actual);
}
public static void assertXmlSimilar(CharSequence expected, CharSequence actual) throws SAXException, IOException {
Diff diff = new Diff(expected.toString(), actual.toString());
diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier());
assertXMLEqual(diff, true);
}
}

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

@ -210,6 +210,7 @@ public final class Socks5Proxy {
if (this.serverSocket != null) {
this.serverThread = new Thread(this.serverProcess);
this.serverThread.setName("Smack Local SOCKS5 Proxy");
this.serverThread.start();
}
}

View File

@ -16,7 +16,12 @@
*/
package org.jivesoftware.smackx.forward.packet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.XmlStringBuilder;
@ -112,4 +117,21 @@ public class Forwarded implements ExtensionElement {
public static Forwarded from(Stanza packet) {
return packet.getExtension(ELEMENT, NAMESPACE);
}
/**
* Extract messages in a collection of forwarded elements. Note that it is required that the {@link Forwarded} in
* the given collection only contain {@link Message} stanzas.
*
* @param forwardedCollection the collection to extract from.
* @return a list a the extracted messages.
* @since 4.3.0
*/
public static List<Message> extractMessagesFrom(Collection<Forwarded> forwardedCollection) {
List<Message> res = new ArrayList<>(forwardedCollection.size());
for (Forwarded forwarded : forwardedCollection) {
Message message = (Message) forwarded.forwardedPacket;
res.add(message);
}
return res;
}
}

View File

@ -57,7 +57,7 @@ import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
@ -149,7 +149,7 @@ public class MultiUserChat {
private final StanzaListener declinesListener;
private String subject;
private Resourcepart nickname;
private EntityFullJid myRoomJid;
private boolean joined = false;
private StanzaCollector messageCollector;
@ -208,7 +208,7 @@ public class MultiUserChat {
LOGGER.warning("Presence not from a full JID: " + presence.getFrom());
return;
}
final String myRoomJID = MultiUserChat.this.room + "/" + nickname;
final EntityFullJid myRoomJID = myRoomJid;
final boolean isUserStatusModification = presence.getFrom().equals(myRoomJID);
asyncButOrdered.performAsyncButOrdered(MultiUserChat.this, new Runnable() {
@ -383,7 +383,9 @@ public class MultiUserChat {
// This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may
// performed roomnick rewriting
this.nickname = presence.getFrom().asEntityFullJidIfPossible().getResourcepart();
Resourcepart receivedNickname = presence.getFrom().getResourceOrThrow();
setNickname(receivedNickname);
joined = true;
// Update the list of joined rooms
@ -391,6 +393,10 @@ public class MultiUserChat {
return presence;
}
private void setNickname(Resourcepart nickname) {
this.myRoomJid = JidCreate.entityFullFrom(room, nickname);
}
/**
* Get a new MUC enter configuration builder.
*
@ -486,6 +492,7 @@ public class MultiUserChat {
* @deprecated use {@link #createOrJoin(MucEnterConfiguration)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public MucCreateConfigFormHandle createOrJoin(Resourcepart nickname, String password, @SuppressWarnings("deprecation") DiscussionHistory history, long timeout)
throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException, NotAMucServiceException {
MucEnterConfiguration.Builder builder = getEnterConfigurationBuilder(nickname).withPassword(
@ -684,6 +691,7 @@ public class MultiUserChat {
* @deprecated use {@link #join(MucEnterConfiguration)} instead.
*/
@Deprecated
// TODO Remove in Smack 4.4
public void join(
Resourcepart nickname,
String password,
@ -756,10 +764,15 @@ public class MultiUserChat {
// throw.
userHasLeft();
final EntityFullJid myRoomJid = this.myRoomJid;
if (myRoomJid == null) {
return;
}
// We leave a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence leavePresence = new Presence(Presence.Type.unavailable);
leavePresence.setTo(JidCreate.fullFrom(room, nickname));
leavePresence.setTo(myRoomJid);
connection.sendStanza(leavePresence);
}
@ -1096,7 +1109,11 @@ public class MultiUserChat {
* @return the nickname currently being used.
*/
public Resourcepart getNickname() {
return nickname;
final EntityFullJid myRoomJid = this.myRoomJid;
if (myRoomJid == null) {
return null;
}
return myRoomJid.getResourcepart();
}
/**
@ -1114,7 +1131,7 @@ public class MultiUserChat {
* @throws MucNotJoinedException
*/
public synchronized void changeNickname(Resourcepart nickname) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, MucNotJoinedException {
StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
Objects.requireNonNull(nickname, "Nickname must not be null or blank.");
// Check that we already have joined the room before attempting to change the
// nickname.
if (!joined) {
@ -1137,7 +1154,8 @@ public class MultiUserChat {
// exception will be thrown
response.nextResultOrThrow();
this.nickname = nickname;
// TODO: Shouldn't this handle nickname rewriting by the MUC service?
setNickname(nickname);
}
/**
@ -1152,7 +1170,11 @@ public class MultiUserChat {
* @throws MucNotJoinedException
*/
public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException, MucNotJoinedException {
StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
final EntityFullJid myRoomJid = this.myRoomJid;
if (myRoomJid == null) {
throw new MucNotJoinedException(this);
}
// Check that we already have joined the room before attempting to change the
// availability status.
if (!joined) {
@ -1163,7 +1185,7 @@ public class MultiUserChat {
Presence joinPresence = new Presence(Presence.Type.available);
joinPresence.setStatus(status);
joinPresence.setMode(mode);
joinPresence.setTo(JidCreate.fullFrom(room, nickname));
joinPresence.setTo(myRoomJid);
// Send join packet.
connection.sendStanza(joinPresence);
@ -2398,7 +2420,7 @@ public class MultiUserChat {
// Reset occupant information.
occupantsMap.clear();
nickname = null;
myRoomJid = null;
userHasLeft();
}
else {
@ -2418,7 +2440,7 @@ public class MultiUserChat {
// Reset occupant information.
occupantsMap.clear();
nickname = null;
myRoomJid = null;
userHasLeft();
}
}
@ -2437,11 +2459,21 @@ public class MultiUserChat {
// Reset occupant information.
occupantsMap.clear();
nickname = null;
myRoomJid = null;
userHasLeft();
}
}
/**
* Get the XMPP connection associated with this chat instance.
*
* @return the associated XMPP connection.
* @since 4.3.0
*/
public XMPPConnection getXmppConnection() {
return connection;
}
@Override
public String toString() {
return "MUC: " + room + "(" + connection.getUser() + ")";

View File

@ -19,7 +19,6 @@ package org.jivesoftware.smackx.muc;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -46,6 +45,7 @@ import org.jivesoftware.smack.filter.StanzaTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.CleaningWeakReferenceMap;
import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
@ -144,7 +144,7 @@ public final class MultiUserChatManager extends Manager {
* those instances to get garbage collected. Note that MultiUserChat instances can not get garbage collected while
* the user is joined, because then the MUC will have PacketListeners added to the XMPPConnection.
*/
private final Map<EntityBareJid, WeakReference<MultiUserChat>> multiUserChats = new HashMap<>();
private final Map<EntityBareJid, WeakReference<MultiUserChat>> multiUserChats = new CleaningWeakReferenceMap<>();
private boolean autoJoinOnReconnect;

View File

@ -17,8 +17,10 @@
package org.jivesoftware.smackx.xdata;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.jivesoftware.smack.packet.NamedElement;
@ -27,6 +29,8 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.xdatavalidation.packet.ValidateElement;
import org.jxmpp.util.XmppDateTime;
/**
* Represents a field of a form. The field could be used to represent a question to complete,
* a completed question or a data returned from a search. The exact interpretation of the field
@ -266,6 +270,21 @@ public class FormField implements NamedElement {
return firstValue.toString();
}
/**
* Parses the first value of this form field as XEP-0082 date/time format and returns a date instance or {@code null}.
*
* @return a Date instance representing the date/time information of the first value of this field.
* @throws ParseException if parsing fails.
* @since 4.3.0
*/
public Date getFirstValueAsDate() throws ParseException {
String valueString = getFirstValue();
if (valueString == null) {
return null;
}
return XmppDateTime.parseXEP0082Date(valueString);
}
/**
* Returns the variable name that the question is filling out.
* <p>
@ -361,6 +380,18 @@ public class FormField implements NamedElement {
}
}
/**
* Adds the given Date as XEP-0082 formated string by invoking {@link #addValue(CharSequence)} after the date
* instance was formated.
*
* @param date the date instance to add as XEP-0082 formated string.
* @since 4.3.0
*/
public void addValue(Date date) {
String dateString = XmppDateTime.formatXEP0082Date(date);
addValue(dateString);
}
/**
* Adds a default values to the question if the question is part of a form to fill out.
* Otherwise, adds an answered values to the question.
@ -378,7 +409,7 @@ public class FormField implements NamedElement {
*/
protected void resetValues() {
synchronized (values) {
values.removeAll(new ArrayList<>(values));
values.clear();
}
}

View File

@ -18,6 +18,7 @@
package org.jivesoftware.smackx.xdata.packet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@ -234,6 +235,26 @@ public class DataForm implements ExtensionElement {
}
}
/**
* Add the given fields to this form.
*
* @param fieldsToAdd
* @return true if a field was overridden.
* @since 4.3.0
*/
public boolean addFields(Collection<FormField> fieldsToAdd) {
boolean fieldOverridden = false;
synchronized (fields) {
for (FormField field : fieldsToAdd) {
FormField previousField = fields.put(field.getVariable(), field);
if (previousField != null) {
fieldOverridden = true;
}
}
}
return fieldOverridden;
}
/**
* Adds a new instruction to the list of instructions that explain how to fill out the form
* and what the form is about. The dataform could include multiple instructions since each

View File

@ -0,0 +1,50 @@
/**
*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.commands.provider;
import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smackx.commands.AdHocCommand;
import org.jivesoftware.smackx.commands.packet.AdHocCommandData;
import org.junit.Test;
public class CommandsProviderTest {
@Test
public void parseErrorWithRequest() throws Exception {
final String errorWithRequest = "<iq id='sid' type='error' from='from@example.com' to='to@example.com'>"
+ "<command xmlns='http://jabber.org/protocol/commands' node='http://example.com' action='execute'>"
+ "</command>" + "<error type='cancel'>"
+ "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" + "</error>" + "</iq>";
final Stanza requestStanza = PacketParserUtils.parseStanza(errorWithRequest);
final AdHocCommandData adHocIq = (AdHocCommandData) requestStanza;
assertEquals(IQ.Type.error, adHocIq.getType());
assertEquals(AdHocCommand.Action.execute, adHocIq.getAction());
StanzaError error = adHocIq.getError();
assertEquals(StanzaError.Type.CANCEL, error.getType());
assertEquals(StanzaError.Condition.bad_request, error.getCondition());
}
}

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)));
}