1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-11-25 07:42:06 +01:00

Merge Smack 4.1.0-rc2

Conflicts:
	smack-core/src/main/java/org/jivesoftware/smack/filter/FromMatchesFilter.java
	smack-extensions/src/main/java/org/jivesoftware/smackx/iqregister/AccountManager.java
	version.gradle
This commit is contained in:
Florian Schmaus 2015-02-21 18:07:45 +01:00
commit fbf0ba13ce
48 changed files with 650 additions and 166 deletions

View file

@ -95,6 +95,7 @@ allprojects {
options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('Xdoclint:none', '-quiet')
} }
} }
} }
gradle.taskGraph.whenReady { taskGraph -> gradle.taskGraph.whenReady { taskGraph ->

View file

@ -46,7 +46,7 @@ own filters by coding to the `PacketFilter` interface. The default set of
filters includes: filters includes:
* `PacketTypeFilter` -- filters for packets that are a particular Class type. * `PacketTypeFilter` -- filters for packets that are a particular Class type.
* `PacketIDFilter` -- filters for packets with a particular packet ID. * `StanzaIdFilter` -- filters for packets with a particular packet ID.
* `ThreadFilter` -- filters for message packets with a particular thread ID. * `ThreadFilter` -- filters for message packets with a particular thread ID.
* `ToContainsFilter` -- filters for packets that are sent to a particular address. * `ToContainsFilter` -- filters for packets that are sent to a particular address.
* `FromContainsFilter` -- filters for packets that are sent to a particular address. * `FromContainsFilter` -- filters for packets that are sent to a particular address.

View file

@ -141,6 +141,17 @@ hr {
<div id="pageBody"> <div id="pageBody">
<h2>4.0.7 -- <span style="font-weight: normal;">2015-02-20</span></h2>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://igniterealtime.org/issues/browse/SMACK-635'>SMACK-635</a>] - Typo DNSUtil.init() prevents DNS SRV lookups to fail in some cases
</li>
<li>[<a href='https://igniterealtime.org/issues/browse/SMACK-643'>SMACK-643</a>] - Smack should not set the service name to the vale of the &#39;from&#39; attribute of the opening stream element received from the service
</li>
</ul>
<h2>4.0.6 -- <span style="font-weight: normal;">2014-11-23</span></h2> <h2>4.0.6 -- <span style="font-weight: normal;">2014-11-23</span></h2>
<h2> Bug <h2> Bug

View file

@ -7,9 +7,10 @@ smack-extensions and smack-experimental."""
// Note that the test dependencies (junit, ) are inferred from the // Note that the test dependencies (junit, ) are inferred from the
// sourceSet.test of the core subproject // sourceSet.test of the core subproject
dependencies { dependencies {
// androidProjects lists all projects that are checked to compile against android.jar
// Filter out the optional Smack dependencies from androidProjects // Filter out the optional Smack dependencies from androidProjects
androidProjects.findAll { androidProjects.findAll {
![':smack-tcp', ':smack-extensions', ':smack-experimental'].contains(it.getPath()) ![':smack-tcp', ':smack-extensions', ':smack-experimental', ':smack-bosh'].contains(it.getPath())
}.each { project -> }.each { project ->
compile project compile project
} }

View file

@ -4,5 +4,5 @@ This API is considered beta quality."""
dependencies { dependencies {
compile project(':smack-core') compile project(':smack-core')
compile 'org.igniterealtime.jbosh:jbosh:(0.8,0.9]' compile 'org.igniterealtime.jbosh:jbosh:[0.8,0.9)'
} }

View file

@ -39,7 +39,6 @@ import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.PlainStreamElement; import org.jivesoftware.smack.packet.PlainStreamElement;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
@ -289,19 +288,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
connected = false; connected = false;
isFirstInitialization = false; isFirstInitialization = false;
Presence unavailablePresence = new Presence(Type.unavailable);
try {
client.disconnect(ComposableBody.builder()
.setNamespaceDefinition("xmpp", XMPP_BOSH_NS)
.setPayloadXML(unavailablePresence.toXML().toString())
.build());
// Wait 150 ms for processes to clean-up, then shutdown.
Thread.sleep(150);
}
catch (Exception e) {
// Ignore.
}
// Close down the readers and writers. // Close down the readers and writers.
if (readerPipe != null) { if (readerPipe != null) {
try { try {

View file

@ -4,5 +4,5 @@ Allow to compress the XMPP stream with help of jzlib."""
dependencies { dependencies {
compile project(path: ':smack-core') compile project(path: ':smack-core')
compile 'com.jcraft:jzlib:(1.1,1.2]' compile 'com.jcraft:jzlib:[1.1,1.2)'
} }

View file

@ -57,7 +57,7 @@ import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.debugger.SmackDebugger; import org.jivesoftware.smack.debugger.SmackDebugger;
import org.jivesoftware.smack.filter.IQReplyFilter; import org.jivesoftware.smack.filter.IQReplyFilter;
import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.StanzaIdFilter;
import org.jivesoftware.smack.iqrequest.IQRequestHandler; import org.jivesoftware.smack.iqrequest.IQRequestHandler;
import org.jivesoftware.smack.packet.Bind; import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.ErrorIQ; import org.jivesoftware.smack.packet.ErrorIQ;
@ -547,7 +547,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// Note that we can not use IQReplyFilter here, since the users full JID is not yet // Note that we can not use IQReplyFilter here, since the users full JID is not yet
// available. It will become available right after the resource has been successfully bound. // available. It will become available right after the resource has been successfully bound.
Bind bindResource = Bind.newSet(resource); Bind bindResource = Bind.newSet(resource);
PacketCollector packetCollector = createPacketCollectorAndSend(new PacketIDFilter(bindResource), bindResource); PacketCollector packetCollector = createPacketCollectorAndSend(new StanzaIdFilter(bindResource), bindResource);
Bind response = packetCollector.nextResultOrThrow(); Bind response = packetCollector.nextResultOrThrow();
// Set the connections user to the result of resource binding. It is important that we don't infer the user // Set the connections user to the result of resource binding. It is important that we don't infer the user
// from the login() arguments and the configurations service name, as, for example, when SASL External is used, // from the login() arguments and the configurations service name, as, for example, when SASL External is used,
@ -560,7 +560,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// For more information see http://tools.ietf.org/html/draft-cridland-xmpp-session-01 // For more information see http://tools.ietf.org/html/draft-cridland-xmpp-session-01
if (sessionFeature != null && !sessionFeature.isOptional() && !getConfiguration().isLegacySessionDisabled()) { if (sessionFeature != null && !sessionFeature.isOptional() && !getConfiguration().isLegacySessionDisabled()) {
Session session = new Session(); Session session = new Session();
packetCollector = createPacketCollectorAndSend(new PacketIDFilter(session), session); packetCollector = createPacketCollectorAndSend(new StanzaIdFilter(session), session);
packetCollector.nextResultOrThrow(); packetCollector.nextResultOrThrow();
} }
} }
@ -1445,7 +1445,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
@Override @Override
public void sendStanzaWithResponseCallback(Stanza stanza, PacketFilter replyFilter, public void sendStanzaWithResponseCallback(Stanza stanza, final PacketFilter replyFilter,
final PacketListener callback, final ExceptionCallback exceptionCallback, final PacketListener callback, final ExceptionCallback exceptionCallback,
long timeout) throws NotConnectedException, InterruptedException { long timeout) throws NotConnectedException, InterruptedException {
Objects.requireNonNull(stanza, "stanza must not be null"); Objects.requireNonNull(stanza, "stanza must not be null");
@ -1478,7 +1478,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// If the packetListener got removed, then it was never run and // If the packetListener got removed, then it was never run and
// we never received a response, inform the exception callback // we never received a response, inform the exception callback
if (removed && exceptionCallback != null) { if (removed && exceptionCallback != null) {
exceptionCallback.processException(new NoResponseException(AbstractXMPPConnection.this)); exceptionCallback.processException(NoResponseException.newWith(AbstractXMPPConnection.this, replyFilter));
} }
} }
}, timeout, TimeUnit.MILLISECONDS); }, timeout, TimeUnit.MILLISECONDS);

View file

@ -205,7 +205,7 @@ public class PacketCollector {
P result = nextResult(timeout); P result = nextResult(timeout);
cancel(); cancel();
if (result == null) { if (result == null) {
throw new NoResponseException(connection); throw NoResponseException.newWith(connection, this);
} }
XMPPErrorException.ifHasErrorThenThrow(result); XMPPErrorException.ifHasErrorThenThrow(result);

View file

@ -196,7 +196,7 @@ public class SASLAuthentication {
maybeThrowException(); maybeThrowException();
if (!authenticationSuccessful) { if (!authenticationSuccessful) {
throw new NoResponseException(connection); throw NoResponseException.newWith(connection);
} }
} }
else { else {
@ -239,7 +239,7 @@ public class SASLAuthentication {
maybeThrowException(); maybeThrowException();
if (!authenticationSuccessful) { if (!authenticationSuccessful) {
throw new NoResponseException(connection); throw NoResponseException.newWith(connection);
} }
} }
else { else {
@ -274,7 +274,7 @@ public class SASLAuthentication {
maybeThrowException(); maybeThrowException();
if (!authenticationSuccessful) { if (!authenticationSuccessful) {
throw new NoResponseException(connection); throw NoResponseException.newWith(connection);
} }
} }

View file

@ -19,6 +19,7 @@ package org.jivesoftware.smack;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.util.dns.HostAddress; import org.jivesoftware.smack.util.dns.HostAddress;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
@ -65,10 +66,47 @@ public class SmackException extends Exception {
*/ */
private static final long serialVersionUID = -6523363748984543636L; private static final long serialVersionUID = -6523363748984543636L;
public NoResponseException(XMPPConnection connection) { private final PacketFilter filter;
super("No response received within packet reply timeout. Timeout was " + connection.getPacketReplyTimeout()
+ "ms (~" + connection.getPacketReplyTimeout() / 1000 + "s)"); private NoResponseException(String message, PacketFilter filter) {
super(message);
this.filter = filter;
} }
/**
* Get the filter that was used to collect the response.
*
* @return the used filter or <code>null</code>.
*/
public PacketFilter getFilter() {
return filter;
}
public static NoResponseException newWith(XMPPConnection connection) {
return newWith(connection, (PacketFilter) null);
}
public static NoResponseException newWith(XMPPConnection connection,
PacketCollector collector) {
return newWith(connection, collector.getPacketFilter());
}
public static NoResponseException newWith(XMPPConnection connection, PacketFilter filter) {
final long replyTimeout = connection.getPacketReplyTimeout();
final StringBuilder sb = new StringBuilder(256);
sb.append("No response received within reply timeout. Timeout was "
+ replyTimeout + "ms (~"
+ replyTimeout / 1000 + "s). Used filter: ");
if (filter != null) {
sb.append(filter.toString());
}
else {
sb.append("No filter used or filter was 'null'");
}
sb.append('.');
return new NoResponseException(sb.toString(), filter);
}
} }
public static class NotLoggedInException extends SmackException { public static class NotLoggedInException extends SmackException {

View file

@ -190,7 +190,7 @@ public class SynchronizationPoint<E extends Exception> {
case Initial: case Initial:
case NoResponse: case NoResponse:
case RequestSent: case RequestSent:
throw new NoResponseException(connection); throw NoResponseException.newWith(connection);
default: default:
// Do nothing // Do nothing
break; break;

View file

@ -129,11 +129,6 @@ public abstract class XMPPException extends Exception {
} }
} }
@Override
public String toString() {
return getMessage();
}
public static void ifHasErrorThenThrow(Stanza packet) throws XMPPErrorException { public static void ifHasErrorThenThrow(Stanza packet) throws XMPPErrorException {
XMPPError xmppError = packet.getError(); XMPPError xmppError = packet.getError();
if (xmppError != null) { if (xmppError != null) {
@ -157,7 +152,7 @@ public abstract class XMPPException extends Exception {
* @param streamError the root cause of the exception. * @param streamError the root cause of the exception.
*/ */
public StreamErrorException(StreamError streamError) { public StreamErrorException(StreamError streamError) {
super(); super(streamError.toString());
this.streamError = streamError; this.streamError = streamError;
} }
@ -171,14 +166,5 @@ public abstract class XMPPException extends Exception {
return streamError; return streamError;
} }
@Override
public String getMessage() {
return streamError.toString();
}
@Override
public String toString() {
return getMessage();
}
} }
} }

View file

@ -0,0 +1,78 @@
/**
*
* Copyright 2015 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.smack.filter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jivesoftware.smack.util.Objects;
/**
*
*/
public abstract class AbstractListFilter implements PacketFilter {
/**
* The list of filters.
*/
protected final List<PacketFilter> filters;
/**
* Creates an empty filter.
*/
protected AbstractListFilter() {
filters = new ArrayList<PacketFilter>();
}
/**
* Creates an filter using the specified filters.
*
* @param filters the filters to add.
*/
protected AbstractListFilter(PacketFilter... filters) {
Objects.requireNonNull(filters, "Parameter must not be null.");
for(PacketFilter filter : filters) {
Objects.requireNonNull(filter, "Parameter must not be null.");
}
this.filters = new ArrayList<PacketFilter>(Arrays.asList(filters));
}
/**
* Adds a filter to the filter list. A stanza will pass the filter if all of the filters in the
* list accept it.
*
* @param filter a filter to add to the filter list.
*/
public void addFilter(PacketFilter filter) {
Objects.requireNonNull(filter, "Parameter must not be null.");
filters.add(filter);
}
@Override
public final String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" (");
for (PacketFilter filter : filters) {
sb.append(' ' + filter.toString() + ',');
}
sb.append(")");
return sb.toString();
}
}

View file

@ -17,12 +17,7 @@
package org.jivesoftware.smack.filter; package org.jivesoftware.smack.filter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Objects;
/** /**
* Implements the logical AND operation over two or more packet filters. * Implements the logical AND operation over two or more packet filters.
@ -30,19 +25,14 @@ import org.jivesoftware.smack.util.Objects;
* *
* @author Matt Tucker * @author Matt Tucker
*/ */
public class AndFilter implements PacketFilter { public class AndFilter extends AbstractListFilter implements PacketFilter {
/**
* The list of filters.
*/
private final List<PacketFilter> filters;
/** /**
* Creates an empty AND filter. Filters should be added using the * Creates an empty AND filter. Filters should be added using the
* {@link #addFilter(PacketFilter)} method. * {@link #addFilter(PacketFilter)} method.
*/ */
public AndFilter() { public AndFilter() {
filters = new ArrayList<PacketFilter>(); super();
} }
/** /**
@ -51,22 +41,7 @@ public class AndFilter implements PacketFilter {
* @param filters the filters to add. * @param filters the filters to add.
*/ */
public AndFilter(PacketFilter... filters) { public AndFilter(PacketFilter... filters) {
Objects.requireNonNull(filters, "Parameter must not be null."); super(filters);
for(PacketFilter filter : filters) {
Objects.requireNonNull(filter, "Parameter must not be null.");
}
this.filters = new ArrayList<PacketFilter>(Arrays.asList(filters));
}
/**
* Adds a filter to the filter list for the AND operation. A packet
* will pass the filter if all of the filters in the list accept it.
*
* @param filter a filter to add to the filter list.
*/
public void addFilter(PacketFilter filter) {
Objects.requireNonNull(filter, "Parameter must not be null.");
filters.add(filter);
} }
public boolean accept(Stanza packet) { public boolean accept(Stanza packet) {
@ -78,7 +53,4 @@ public class AndFilter implements PacketFilter {
return true; return true;
} }
public String toString() {
return filters.toString();
}
} }

View file

@ -20,6 +20,7 @@ package org.jivesoftware.smack.filter;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Objects;
/** /**
* Filters for packets of a particular type and allows a custom method to further filter the packets. * Filters for packets of a particular type and allows a custom method to further filter the packets.
@ -31,7 +32,7 @@ public abstract class FlexiblePacketTypeFilter<P extends Stanza> implements Pack
protected final Class<P> packetType; protected final Class<P> packetType;
public FlexiblePacketTypeFilter(Class<P> packetType) { public FlexiblePacketTypeFilter(Class<P> packetType) {
this.packetType = packetType; this.packetType = Objects.requireNonNull(packetType, "Type must not be null");
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -49,4 +50,12 @@ public abstract class FlexiblePacketTypeFilter<P extends Stanza> implements Pack
} }
protected abstract boolean acceptSpecific(P packet); protected abstract boolean acceptSpecific(P packet);
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" (" + packetType.toString() + ')');
return sb.toString();
}
} }

View file

@ -106,6 +106,6 @@ public class FromMatchesFilter implements PacketFilter {
public String toString() { public String toString() {
String matchMode = ignoreResourcepart ? "ignoreResourcepart" : "full"; String matchMode = ignoreResourcepart ? "ignoreResourcepart" : "full";
return "FromMatchesFilter (" +matchMode + "): " + address; return getClass().getSimpleName() + " (" + matchMode + "): " + address;
} }
} }

View file

@ -95,7 +95,7 @@ public class IQReplyFilter implements PacketFilter {
packetId = iqPacket.getStanzaId(); packetId = iqPacket.getStanzaId();
PacketFilter iqFilter = new OrFilter(IQTypeFilter.ERROR, IQTypeFilter.RESULT); PacketFilter iqFilter = new OrFilter(IQTypeFilter.ERROR, IQTypeFilter.RESULT);
PacketFilter idFilter = new PacketIDFilter(iqPacket); PacketFilter idFilter = new StanzaIdFilter(iqPacket);
iqAndIdFilter = new AndFilter(iqFilter, idFilter); iqAndIdFilter = new AndFilter(iqFilter, idFilter);
fromFilter = new OrFilter(); fromFilter = new OrFilter();
fromFilter.addFilter(FromMatchesFilter.createFull(to)); fromFilter.addFilter(FromMatchesFilter.createFull(to));
@ -126,4 +126,12 @@ public class IQReplyFilter implements PacketFilter {
} }
} }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(": iqAndIdFilter (").append(iqAndIdFilter.toString()).append("), ");
sb.append(": fromFilter (").append(fromFilter.toString()).append(')');
return sb.toString();
}
} }

View file

@ -38,4 +38,11 @@ public class IQResultReplyFilter extends IQReplyFilter {
return IQTypeFilter.RESULT.accept(packet); return IQTypeFilter.RESULT.accept(packet);
} }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" (" + super.toString() + ')');
return sb.toString();
}
} }

View file

@ -18,6 +18,7 @@ package org.jivesoftware.smack.filter;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.util.Objects;
/** /**
* A filter for IQ packet types. Returns true only if the packet is an IQ packet * A filter for IQ packet types. Returns true only if the packet is an IQ packet
@ -38,11 +39,16 @@ public class IQTypeFilter extends FlexiblePacketTypeFilter<IQ> {
private IQTypeFilter(IQ.Type type) { private IQTypeFilter(IQ.Type type) {
super(IQ.class); super(IQ.class);
this.type = type; this.type = Objects.requireNonNull(type, "Type must not be null");
} }
@Override @Override
protected boolean acceptSpecific(IQ iq) { protected boolean acceptSpecific(IQ iq) {
return iq.getType() == type; return iq.getType() == type;
} }
@Override
public String toString() {
return getClass().getSimpleName() + ": type=" + type;
}
} }

View file

@ -55,4 +55,8 @@ public class MessageTypeFilter extends FlexiblePacketTypeFilter<Message> {
return message.getType() == type; return message.getType() == type;
} }
@Override
public String toString() {
return getClass().getSimpleName() + ": type=" + type;
}
} }

View file

@ -36,4 +36,8 @@ public class MessageWithBodiesFilter extends FlexiblePacketTypeFilter<Message> {
return !message.getBodies().isEmpty(); return !message.getBodies().isEmpty();
} }
@Override
public String toString() {
return getClass().getSimpleName();
}
} }

View file

@ -36,4 +36,8 @@ public class MessageWithSubjectFilter extends FlexiblePacketTypeFilter<Message>
return message.getSubject() != null; return message.getSubject() != null;
} }
@Override
public String toString() {
return getClass().getSimpleName();
}
} }

View file

@ -18,6 +18,7 @@
package org.jivesoftware.smack.filter; package org.jivesoftware.smack.filter;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Objects;
/** /**
* Implements the logical NOT operation on a packet filter. In other words, packets * Implements the logical NOT operation on a packet filter. In other words, packets
@ -35,10 +36,7 @@ public class NotFilter implements PacketFilter {
* @param filter the filter. * @param filter the filter.
*/ */
public NotFilter(PacketFilter filter) { public NotFilter(PacketFilter filter) {
if (filter == null) { this.filter = Objects.requireNonNull(filter, "Parameter must not be null.");
throw new IllegalArgumentException("Parameter must not be null.");
}
this.filter = filter;
} }
public boolean accept(Stanza packet) { public boolean accept(Stanza packet) {

View file

@ -17,12 +17,7 @@
package org.jivesoftware.smack.filter; package org.jivesoftware.smack.filter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Objects;
/** /**
* Implements the logical OR operation over two or more packet filters. In * Implements the logical OR operation over two or more packet filters. In
@ -30,19 +25,14 @@ import org.jivesoftware.smack.util.Objects;
* *
* @author Matt Tucker * @author Matt Tucker
*/ */
public class OrFilter implements PacketFilter { public class OrFilter extends AbstractListFilter implements PacketFilter {
/**
* The list of filters.
*/
private final List<PacketFilter> filters;
/** /**
* Creates an empty OR filter. Filters should be added using the * Creates an empty OR filter. Filters should be added using the
* {@link #addFilter(PacketFilter)} method. * {@link #addFilter(PacketFilter)} method.
*/ */
public OrFilter() { public OrFilter() {
filters = new ArrayList<PacketFilter>(); super();
} }
/** /**
@ -51,24 +41,10 @@ public class OrFilter implements PacketFilter {
* @param filters the filters to add. * @param filters the filters to add.
*/ */
public OrFilter(PacketFilter... filters) { public OrFilter(PacketFilter... filters) {
Objects.requireNonNull(filters, "Parameter must not be null."); super(filters);
for(PacketFilter filter : filters) {
Objects.requireNonNull(filter, "Parameter must not be null.");
}
this.filters = new ArrayList<PacketFilter>(Arrays.asList(filters));
}
/**
* Adds a filter to the filter list for the OR operation. A packet
* will pass the filter if any filter in the list accepts it.
*
* @param filter a filter to add to the filter list.
*/
public void addFilter(PacketFilter filter) {
Objects.requireNonNull(filter, "Parameter must not be null.");
filters.add(filter);
} }
@Override
public boolean accept(Stanza packet) { public boolean accept(Stanza packet) {
for (PacketFilter filter : filters) { for (PacketFilter filter : filters) {
if (filter.accept(packet)) { if (filter.accept(packet)) {
@ -78,7 +54,4 @@ public class OrFilter implements PacketFilter {
return false; return false;
} }
public String toString() {
return filters.toString();
}
} }

View file

@ -68,4 +68,9 @@ public class PacketExtensionFilter implements PacketFilter {
public boolean accept(Stanza packet) { public boolean accept(Stanza packet) {
return packet.hasExtension(elementName, namespace); return packet.hasExtension(elementName, namespace);
} }
@Override
public String toString() {
return getClass().getSimpleName() + ": element=" + elementName + " namespace=" + namespace;
}
} }

View file

@ -28,7 +28,7 @@ import org.jivesoftware.smack.packet.Stanza;
* packet filtering by using the {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and * packet filtering by using the {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and
* {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible to define * {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible to define
* your own filters by implementing this interface. The code example below creates a trivial filter * your own filters by implementing this interface. The code example below creates a trivial filter
* for packets with a specific ID (real code should use {@link PacketIDFilter} instead). * for packets with a specific ID (real code should use {@link StanzaIdFilter} instead).
* *
* <pre> * <pre>
* // Use an anonymous inner class to define a packet filter that returns * // Use an anonymous inner class to define a packet filter that returns

View file

@ -24,7 +24,9 @@ import org.jivesoftware.smack.util.StringUtils;
* Filters for packets with a particular packet ID. * Filters for packets with a particular packet ID.
* *
* @author Matt Tucker * @author Matt Tucker
* @deprecated use {@link StanzaIdFilter} instead.
*/ */
@Deprecated
public class PacketIDFilter implements PacketFilter { public class PacketIDFilter implements PacketFilter {
private final String packetID; private final String packetID;
@ -33,7 +35,9 @@ public class PacketIDFilter implements PacketFilter {
* Creates a new packet ID filter using the specified packet's ID. * Creates a new packet ID filter using the specified packet's ID.
* *
* @param packet the packet which the ID is taken from. * @param packet the packet which the ID is taken from.
* @deprecated use {@link StanzaIdfilter(Stanza)} instead.
*/ */
@Deprecated
public PacketIDFilter(Stanza packet) { public PacketIDFilter(Stanza packet) {
this(packet.getStanzaId()); this(packet.getStanzaId());
} }
@ -42,7 +46,9 @@ public class PacketIDFilter implements PacketFilter {
* Creates a new packet ID filter using the specified packet ID. * Creates a new packet ID filter using the specified packet ID.
* *
* @param packetID the packet ID to filter for. * @param packetID the packet ID to filter for.
* @deprecated use {@link StanzaIdFilter(String)} instead.
*/ */
@Deprecated
public PacketIDFilter(String packetID) { public PacketIDFilter(String packetID) {
StringUtils.requireNotNullOrEmpty(packetID, "Packet ID must not be null or empty."); StringUtils.requireNotNullOrEmpty(packetID, "Packet ID must not be null or empty.");
this.packetID = packetID; this.packetID = packetID;
@ -53,6 +59,6 @@ public class PacketIDFilter implements PacketFilter {
} }
public String toString() { public String toString() {
return "PacketIDFilter by id: " + packetID; return getClass().getSimpleName() + ": id=" + packetID;
} }
} }

View file

@ -53,7 +53,8 @@ public class PacketTypeFilter implements PacketFilter {
return packetType.isInstance(packet); return packetType.isInstance(packet);
} }
@Override
public String toString() { public String toString() {
return "PacketTypeFilter: " + packetType.getName(); return getClass().getSimpleName() + ": " + packetType.getName();
} }
} }

View file

@ -18,6 +18,7 @@ package org.jivesoftware.smack.filter;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type; import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smack.util.Objects;
/** /**
* A filter for Presence types. Returns true only if the stanza is an Presence packet and it matches the type provided in the * A filter for Presence types. Returns true only if the stanza is an Presence packet and it matches the type provided in the
@ -38,11 +39,16 @@ public class PresenceTypeFilter extends FlexiblePacketTypeFilter<Presence> {
private PresenceTypeFilter(Presence.Type type) { private PresenceTypeFilter(Presence.Type type) {
super(Presence.class); super(Presence.class);
this.type = type; this.type = Objects.requireNonNull(type, "type must not be null");
} }
@Override @Override
protected boolean acceptSpecific(Presence presence) { protected boolean acceptSpecific(Presence presence) {
return presence.getType() == type; return presence.getType() == type;
} }
@Override
public String toString() {
return getClass().getSimpleName() + ": type=" + type;
}
} }

View file

@ -0,0 +1,57 @@
/**
*
* Copyright 2003-2007 Jive Software.
*
* 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.filter;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.StringUtils;
/**
* Filters for Stanzas with a particular stanza ID.
*
* @author Matt Tucker
*/
public class StanzaIdFilter implements PacketFilter {
private final String stanzaId;
/**
* Creates a new stanza ID filter using the specified stanza's ID.
*
* @param stanza the stanza which the ID is taken from.
*/
public StanzaIdFilter(Stanza stanza) {
this(stanza.getStanzaId());
}
/**
* Creates a new stanza ID filter using the specified stanza ID.
*
* @param stanzaID the stanza ID to filter for.
*/
public StanzaIdFilter(String stanzaID) {
this.stanzaId = StringUtils.requireNotNullOrEmpty(stanzaID, "Stanza ID must not be null or empty.");
}
public boolean accept(Stanza stanza) {
return stanzaId.equals(stanza.getStanzaId());
}
public String toString() {
return getClass().getSimpleName() + ": id=" + stanzaId;
}
}

View file

@ -17,7 +17,6 @@
package org.jivesoftware.smack.filter; package org.jivesoftware.smack.filter;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
@ -26,7 +25,7 @@ import org.jivesoftware.smack.util.StringUtils;
* *
* @author Matt Tucker * @author Matt Tucker
*/ */
public class ThreadFilter implements PacketFilter { public class ThreadFilter extends FlexiblePacketTypeFilter<Message> implements PacketFilter {
private final String thread; private final String thread;
@ -40,7 +39,13 @@ public class ThreadFilter implements PacketFilter {
this.thread = thread; this.thread = thread;
} }
public boolean accept(Stanza packet) { @Override
return packet instanceof Message && thread.equals(((Message) packet).getThread()); protected boolean acceptSpecific(Message message) {
return thread.equals(message.getThread());
}
@Override
public String toString() {
return getClass().getSimpleName() + ": thread=" + thread;
} }
} }

View file

@ -36,4 +36,8 @@ public class ToFilter implements PacketFilter {
return packetTo.equals(to); return packetTo.equals(to);
} }
@Override
public String toString() {
return getClass().getSimpleName() + ": to=" + to;
}
} }

View file

@ -5,6 +5,6 @@ Connect your favourite slf4j backend of choice to get output inside of it"""
dependencies { dependencies {
compile project(':smack-core') compile project(':smack-core')
compile 'org.slf4j:slf4j-api:(1.7,1.8]' compile 'org.slf4j:slf4j-api:[1.7,1.8)'
testCompile project(':smack-core').sourceSets.test.runtimeClasspath testCompile project(':smack-core').sourceSets.test.runtimeClasspath
} }

View file

@ -176,11 +176,8 @@ public class FaultTolerantNegotiator extends StreamNegotiator {
this.collector = collector; this.collector = collector;
} }
public InputStream call() throws XMPPErrorException, InterruptedException, SmackException { public InputStream call() throws XMPPErrorException, InterruptedException, NoResponseException, SmackException {
Stanza streamInitiation = collector.nextResult(); Stanza streamInitiation = collector.nextResultOrThrow();
if (streamInitiation == null) {
throw new NoResponseException(connection);
}
StreamNegotiator negotiator = determineNegotiator(streamInitiation); StreamNegotiator negotiator = determineNegotiator(streamInitiation);
return negotiator.negotiateIncomingStream(streamInitiation); return negotiator.negotiateIncomingStream(streamInitiation);
} }

View file

@ -32,6 +32,7 @@ import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager;
import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest;
import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession;
@ -128,14 +129,11 @@ public class Socks5TransferNegotiator extends StreamNegotiator {
*/ */
private static class BytestreamSIDFilter extends PacketTypeFilter { private static class BytestreamSIDFilter extends PacketTypeFilter {
private String sessionID; private final String sessionID;
public BytestreamSIDFilter(String sessionID) { public BytestreamSIDFilter(String sessionID) {
super(Bytestream.class); super(Bytestream.class);
if (sessionID == null) { this.sessionID = Objects.requireNonNull(sessionID, "SessionID cannot be null");
throw new IllegalArgumentException("StreamID cannot be null");
}
this.sessionID = sessionID;
} }
@Override @Override

View file

@ -22,6 +22,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.PacketCollector; import org.jivesoftware.smack.PacketCollector;
@ -31,7 +32,7 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.StanzaIdFilter;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.iqregister.packet.Registration; import org.jivesoftware.smackx.iqregister.packet.Registration;
@ -41,6 +42,9 @@ import org.jivesoftware.smackx.iqregister.packet.Registration;
* @author Matt Tucker * @author Matt Tucker
*/ */
public class AccountManager extends Manager { public class AccountManager extends Manager {
private static final Logger LOGGER = Logger.getLogger(AccountManager.class.getName());
private static final Map<XMPPConnection, AccountManager> INSTANCES = new WeakHashMap<XMPPConnection, AccountManager>(); private static final Map<XMPPConnection, AccountManager> INSTANCES = new WeakHashMap<XMPPConnection, AccountManager>();
/** /**
@ -58,6 +62,35 @@ public class AccountManager extends Manager {
return accountManager; return accountManager;
} }
private static boolean allowSensitiveOperationOverInsecureConnectionDefault = false;
/**
* The default value used by new account managers for <code>allowSensitiveOperationOverInsecureConnection</code>.
*
* @param allow
* @see #sensitiveOperationOverInsecureConnection(boolean)
* @since 4.1
*/
public static void sensitiveOperationOverInsecureConnectionDefault(boolean allow) {
AccountManager.allowSensitiveOperationOverInsecureConnectionDefault = allow;
}
private boolean allowSensitiveOperationOverInsecureConnection = allowSensitiveOperationOverInsecureConnectionDefault;
/**
* Set to <code>true</code> to allow sensitive operation over insecure connection.
* <p>
* Set to true to allow sensitive operations like account creation or password changes over an insecure (e.g.
* unencrypted) connections.
* </p>
*
* @param allow
* @since 4.1
*/
public void sensitiveOperationOverInsecureConnection(boolean allow) {
this.allowSensitiveOperationOverInsecureConnection = allow;
}
private Registration info = null; private Registration info = null;
/** /**
@ -231,6 +264,11 @@ public class AccountManager extends Manager {
*/ */
public void createAccount(String username, String password, Map<String, String> attributes) public void createAccount(String username, String password, Map<String, String> attributes)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
if (!connection().isSecureConnection() && !allowSensitiveOperationOverInsecureConnection) {
// TODO throw exception in newer Smack versions
LOGGER.warning("Creating account over insecure connection. "
+ "This will throw an exception in future versions of Smack if AccountManager.sensitiveOperationOverInsecureConnection(true) is not set");
}
attributes.put("username", username); attributes.put("username", username);
attributes.put("password", password); attributes.put("password", password);
Registration reg = new Registration(attributes); Registration reg = new Registration(attributes);
@ -251,6 +289,11 @@ public class AccountManager extends Manager {
* @throws InterruptedException * @throws InterruptedException
*/ */
public void changePassword(String newPassword) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { public void changePassword(String newPassword) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
if (!connection().isSecureConnection() && !allowSensitiveOperationOverInsecureConnection) {
// TODO throw exception in newer Smack versions
LOGGER.warning("Changing password over insecure connection. "
+ "This will throw an exception in future versions of Smack if AccountManager.sensitiveOperationOverInsecureConnection(true) is not set");
}
Map<String, String> map = new HashMap<String, String>(); Map<String, String> map = new HashMap<String, String>();
map.put("username", connection().getUser().getLocalpart().toString()); map.put("username", connection().getUser().getLocalpart().toString());
map.put("password",newPassword); map.put("password",newPassword);
@ -298,7 +341,7 @@ public class AccountManager extends Manager {
} }
private PacketCollector createPacketCollectorAndSend(IQ req) throws NotConnectedException, InterruptedException { private PacketCollector createPacketCollectorAndSend(IQ req) throws NotConnectedException, InterruptedException {
PacketCollector collector = connection().createPacketCollectorAndSend(new PacketIDFilter(req.getStanzaId()), req); PacketCollector collector = connection().createPacketCollectorAndSend(new StanzaIdFilter(req.getStanzaId()), req);
return collector; return collector;
} }
} }

View file

@ -27,26 +27,6 @@ import org.jxmpp.jid.Jid;
* A Version IQ packet, which is used by XMPP clients to discover version information * A Version IQ packet, which is used by XMPP clients to discover version information
* about the software running at another entity's JID.<p> * about the software running at another entity's JID.<p>
* *
* An example to discover the version of the server:
* <pre>
* // Request the version from the server.
* Version versionRequest = new Version();
* timeRequest.setType(IQ.Type.get);
* timeRequest.setTo("example.com");
*
* // Create a packet collector to listen for a response.
* PacketCollector collector = con.createPacketCollector(
* new PacketIDFilter(versionRequest.getStanzaId()));
*
* con.sendPacket(versionRequest);
*
* // Wait up to 5 seconds for a result.
* IQ result = (IQ)collector.nextResult(5000);
* if (result != null && result.getType() == IQ.Type.result) {
* Version versionResult = (Version)result;
* // Do something with result...
* }</pre><p>
*
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class Version extends IQ { public class Version extends IQ {

View file

@ -57,14 +57,15 @@ public class ItemProvider extends PacketExtensionProvider<Item>
String payloadElemName = parser.getName(); String payloadElemName = parser.getName();
String payloadNS = parser.getNamespace(); String payloadNS = parser.getNamespace();
if (ProviderManager.getExtensionProvider(payloadElemName, payloadNS) == null) final PacketExtensionProvider<PacketExtension> extensionProvider = ProviderManager.getExtensionProvider(payloadElemName, payloadNS);
if (extensionProvider == null)
{ {
CharSequence payloadText = PacketParserUtils.parseElement(parser, true); CharSequence payloadText = PacketParserUtils.parseElement(parser, true);
return new PayloadItem<SimplePayload>(id, node, new SimplePayload(payloadElemName, payloadNS, payloadText)); return new PayloadItem<SimplePayload>(id, node, new SimplePayload(payloadElemName, payloadNS, payloadText));
} }
else else
{ {
return new PayloadItem<PacketExtension>(id, node, PacketParserUtils.parsePacketExtension(payloadElemName, payloadNS, parser)); return new PayloadItem<PacketExtension>(id, node, extensionProvider.parse(parser));
} }
} }
} }

View file

@ -51,7 +51,7 @@ import org.jivesoftware.smack.packet.Stanza;
* <code> * <code>
* public void methodToTest() { * public void methodToTest() {
* Packet packet = new Packet(); // create an XMPP packet * Packet packet = new Packet(); // create an XMPP packet
* PacketCollector collector = connection.createPacketCollector(new PacketIDFilter()); * PacketCollector collector = connection.createPacketCollector(new StanzaIdFilter());
* connection.sendPacket(packet); * connection.sendPacket(packet);
* Packet reply = collector.nextResult(); * Packet reply = collector.nextResult();
* } * }

View file

@ -142,6 +142,11 @@ public class Roster extends Manager {
*/ */
private final Map<Jid, Map<Resourcepart, Presence>> presenceMap = new ConcurrentHashMap<>(); private final Map<Jid, Map<Resourcepart, Presence>> presenceMap = new ConcurrentHashMap<>();
/**
* Listeners called when the Roster was loaded.
*/
private final Set<RosterLoadedListener> rosterLoadedListeners = new LinkedHashSet<>();
/** /**
* Mutually exclude roster listener invocation and changing the {@link entries} map. Also used * Mutually exclude roster listener invocation and changing the {@link entries} map. Also used
* to synchronize access to either the roster listeners or the entries map. * to synchronize access to either the roster listeners or the entries map.
@ -391,6 +396,34 @@ public class Roster extends Manager {
} }
} }
/**
* Add a roster loaded listener.
*
* @param rosterLoadedListener the listener to add.
* @return true if the listener was not already added.
* @see RosterLoadedListener
* @since 4.1
*/
public boolean addRosterLoadedListener(RosterLoadedListener rosterLoadedListener) {
synchronized (rosterLoadedListener) {
return rosterLoadedListeners.add(rosterLoadedListener);
}
}
/**
* Remove a roster loaded listener.
*
* @param rosterLoadedListener the listener to remove.
* @return true if the listener was active and got removed.
* @see RosterLoadedListener
* @since 4.1
*/
public boolean removeRosterLoadedListener(RosterLoadedListener rosterLoadedListener) {
synchronized (rosterLoadedListener) {
return rosterLoadedListeners.remove(rosterLoadedListener);
}
}
/** /**
* Creates a new group.<p> * Creates a new group.<p>
* <p/> * <p/>
@ -1331,6 +1364,22 @@ public class Roster extends Manager {
} }
// Fire event for roster listeners. // Fire event for roster listeners.
fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries); fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);
// Call the roster loaded listeners after the roster events have been fired. This is
// imporant because the user may call getEntriesAndAddListener() in onRosterLoaded(),
// and if the order would be the other way around, the roster listener added by
// getEntriesAndAddListener() would be invoked with information that was already
// available at the time getEntriesAndAddListenr() was called.
try {
synchronized (rosterLoadedListeners) {
for (RosterLoadedListener rosterLoadedListener : rosterLoadedListeners) {
rosterLoadedListener.onRosterLoaded(Roster.this);
}
}
}
catch (Exception e) {
LOGGER.log(Level.WARNING, "RosterLoadedListener threw exception", e);
}
} }
} }

View file

@ -0,0 +1,38 @@
/**
*
* Copyright 2015 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.smack.roster;
/**
* Roster loaded listeners are invoked once the {@link Roster} was successfully loaded.
* <p>
* A common approach is to call
* {@link Roster#getEntriesAndAddListener(RosterListener, RosterEntries)} within
* {@link #onRosterLoaded(Roster)}, to initialize or update your UI components with the current
* roster state.
* </p>
*/
public interface RosterLoadedListener {
/**
* Called when the Roster was loaded successfully.
*
* @param roster the Roster that was loaded successfully.
*/
public void onRosterLoaded(Roster roster);
}

View file

@ -5,5 +5,5 @@ javax.naming API (e.g. Android)."""
dependencies { dependencies {
compile project(path: ':smack-core') compile project(path: ':smack-core')
compile 'dnsjava:dnsjava:(2.1,2.2]' compile 'dnsjava:dnsjava:[2.1,2.2)'
} }

View file

@ -5,6 +5,6 @@ javax.naming API (e.g. Android)."""
dependencies { dependencies {
compile project(path: ':smack-core') compile project(path: ':smack-core')
compile 'de.measite.minidns:minidns:(0.1,0.2]' compile 'de.measite.minidns:minidns:[0.1,0.2)'
compile "org.jxmpp:jxmpp-util-cache:$jxmppVersion" compile "org.jxmpp:jxmpp-util-cache:$jxmppVersion"
} }

View file

@ -16,7 +16,11 @@
*/ */
package org.jivesoftware.smack.sm; package org.jivesoftware.smack.sm;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.packet.Stanza;
public abstract class StreamManagementException extends SmackException { public abstract class StreamManagementException extends SmackException {
@ -52,5 +56,59 @@ public abstract class StreamManagementException extends SmackException {
super("Stream IDs do not match. Expected '" + expected + "', but got '" + got + "'"); super("Stream IDs do not match. Expected '" + expected + "', but got '" + got + "'");
} }
} }
public static class StreamManagementCounterError extends StreamManagementException {
/**
*
*/
private static final long serialVersionUID = 1L;
private final long handledCount;
private final long previousServerHandledCount;
private final long ackedStanzaCount;
private final int outstandingStanzasCount;
private final List<Stanza> ackedStanzas;
public StreamManagementCounterError(long handledCount, long previousServerHandlerCount,
long ackedStanzaCount,
List<Stanza> ackedStanzas) {
super(
"There was an error regarding the Stream Mangement counters. Server reported "
+ handledCount
+ " handled stanzas, which means that the "
+ ackedStanzaCount
+ " recently send stanzas by client are now acked by the server. But Smack had only "
+ ackedStanzas.size()
+ " to acknowledge. The stanza id of the last acked outstanding stanza is "
+ (ackedStanzas.isEmpty() ? "<no acked stanzas>"
: ackedStanzas.get(ackedStanzas.size() - 1).getStanzaId()));
this.handledCount = handledCount;
this.previousServerHandledCount = previousServerHandlerCount;
this.ackedStanzaCount = ackedStanzaCount;
this.outstandingStanzasCount = ackedStanzas.size();
this.ackedStanzas = Collections.unmodifiableList(ackedStanzas);
}
public long getHandledCount() {
return handledCount;
}
public long getPreviousServerHandledCount() {
return previousServerHandledCount;
}
public long getAckedStanzaCount() {
return ackedStanzaCount;
}
public int getOutstandingStanzasCount() {
return outstandingStanzasCount;
}
public List<Stanza> getAckedStanzas() {
return ackedStanzas;
}
}
} }

View file

@ -0,0 +1,39 @@
/**
*
* Copyright 2015 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.smack.tcp;
import java.util.concurrent.atomic.AtomicBoolean;
public class BundleAndDefer {
private final AtomicBoolean isStopped;
BundleAndDefer(AtomicBoolean isStopped) {
this.isStopped = isStopped;
}
public void stopCurrentBundleAndDefer() {
synchronized (isStopped) {
if (isStopped.get()) {
return;
}
isStopped.set(true);
isStopped.notify();
}
}
}

View file

@ -0,0 +1,49 @@
/**
*
* Copyright 2015 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.smack.tcp;
/**
* This callback is used to get the current value of the period in which Smack does bundle and defer
* outgoing stanzas.
* <p>
* Smack will bundle and defer stanzas if the connection is authenticated, the send queue is empty
* and if a bundle and defer callback is set, either via
* {@link XMPPTCPConnection#setDefaultBundleAndDeferCallback(BundleAndDeferCallback)} or
* {@link XMPPTCPConnection#setBundleandDeferCallback(BundleAndDeferCallback)}, and
* {@link #getBundleAndDeferMillis(BundleAndDefer)} returns a positive value. In a mobile environment, bundling
* and deferring outgoing stanzas may reduce battery consumption. It heavily depends on the
* environment, but recommend values for the bundle and defer period range from 20-60 seconds. But
* keep in mind that longer periods decrease the realtime aspect of Smack.
* </p>
* <p>
* Smack will invoke the callback when it needs to know the length of the bundle and defer period.
* If {@link #getBundleAndDeferMillis(BundleAndDefer)} returns 0 or a negative value, then the
* stanzas will send immediately. You can also prematurely abort the bundling of stanzas by calling
* {@link BundleAndDefer#stopCurrentBundleAndDefer()}.
* </p>
*/
public interface BundleAndDeferCallback {
/**
* Return the bundle and defer period used by Smack in milliseconds.
*
* @param bundleAndDefer used to premature abort bundle and defer.
* @return the bundle and defer period in milliseconds.
*/
public int getBundleAndDeferMillis(BundleAndDefer bundleAndDefer);
}

View file

@ -54,6 +54,7 @@ import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.sm.SMUtils; import org.jivesoftware.smack.sm.SMUtils;
import org.jivesoftware.smack.sm.StreamManagementException; import org.jivesoftware.smack.sm.StreamManagementException;
import org.jivesoftware.smack.sm.StreamManagementException.StreamIdDoesNotMatchException; import org.jivesoftware.smack.sm.StreamManagementException.StreamIdDoesNotMatchException;
import org.jivesoftware.smack.sm.StreamManagementException.StreamManagementCounterError;
import org.jivesoftware.smack.sm.StreamManagementException.StreamManagementNotEnabledException; import org.jivesoftware.smack.sm.StreamManagementException.StreamManagementNotEnabledException;
import org.jivesoftware.smack.sm.packet.StreamManagement; import org.jivesoftware.smack.sm.packet.StreamManagement;
import org.jivesoftware.smack.sm.packet.StreamManagement.AckAnswer; import org.jivesoftware.smack.sm.packet.StreamManagement.AckAnswer;
@ -126,6 +127,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -185,6 +187,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
private final SynchronizationPoint<XMPPException> compressSyncPoint = new SynchronizationPoint<XMPPException>( private final SynchronizationPoint<XMPPException> compressSyncPoint = new SynchronizationPoint<XMPPException>(
this); this);
private static BundleAndDeferCallback defaultBundleAndDeferCallback;
private BundleAndDeferCallback bundleAndDeferCallback = defaultBundleAndDeferCallback;
private static boolean useSmDefault = false; private static boolean useSmDefault = false;
private static boolean useSmResumptionDefault = true; private static boolean useSmResumptionDefault = true;
@ -719,7 +725,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
Socket plain = socket; Socket plain = socket;
// Secure the plain connection // Secure the plain connection
socket = context.getSocketFactory().createSocket(plain, socket = context.getSocketFactory().createSocket(plain,
plain.getInetAddress().getHostAddress(), plain.getPort(), true); host, plain.getPort(), true);
// Initialize the reader and writer with the new secured version // Initialize the reader and writer with the new secured version
initReaderAndWriter(); initReaderAndWriter();
@ -1272,6 +1278,30 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
if (element == null) { if (element == null) {
continue; continue;
} }
// Get a local version of the bundle and defer callback, in case it's unset
// between the null check and the method invocation
final BundleAndDeferCallback localBundleAndDeferCallback = bundleAndDeferCallback;
// If the preconditions are given (e.g. bundleAndDefer callback is set, queue is
// empty), then we could wait a bit for further stanzas attempting to decrease
// our energy consumption
if (localBundleAndDeferCallback != null && isAuthenticated() && queue.isEmpty()) {
final AtomicBoolean bundlingAndDeferringStopped = new AtomicBoolean();
final int bundleAndDeferMillis = localBundleAndDeferCallback.getBundleAndDeferMillis(new BundleAndDefer(
bundlingAndDeferringStopped));
if (bundleAndDeferMillis > 0) {
long remainingWait = bundleAndDeferMillis;
final long waitStart = System.currentTimeMillis();
synchronized (bundlingAndDeferringStopped) {
while (!bundlingAndDeferringStopped.get() && remainingWait > 0) {
bundlingAndDeferringStopped.wait(remainingWait);
remainingWait = bundleAndDeferMillis
- (System.currentTimeMillis() - waitStart);
}
}
}
}
Stanza packet = null; Stanza packet = null;
if (element instanceof Stanza) { if (element instanceof Stanza) {
packet = (Stanza) element; packet = (Stanza) element;
@ -1288,6 +1318,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
writer.flush(); writer.flush();
} }
try { try {
// It is important the we put the stanza in the unacknowledged stanza
// queue before we put it on the wire
unacknowledgedStanzas.put(packet); unacknowledgedStanzas.put(packet);
} }
catch (InterruptedException e) { catch (InterruptedException e) {
@ -1646,7 +1678,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
return Math.min(clientResumptionTime, serverResumptionTime); return Math.min(clientResumptionTime, serverResumptionTime);
} }
private void processHandledCount(long handledCount) throws NotConnectedException { private void processHandledCount(long handledCount) throws NotConnectedException, StreamManagementCounterError {
long ackedStanzasCount = SMUtils.calculateDelta(handledCount, serverHandledStanzasCount); long ackedStanzasCount = SMUtils.calculateDelta(handledCount, serverHandledStanzasCount);
final List<Stanza> ackedStanzas = new ArrayList<Stanza>( final List<Stanza> ackedStanzas = new ArrayList<Stanza>(
handledCount <= Integer.MAX_VALUE ? (int) handledCount handledCount <= Integer.MAX_VALUE ? (int) handledCount
@ -1655,7 +1687,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
Stanza ackedStanza = unacknowledgedStanzas.poll(); Stanza ackedStanza = unacknowledgedStanzas.poll();
// If the server ack'ed a stanza, then it must be in the // If the server ack'ed a stanza, then it must be in the
// unacknowledged stanza queue. There can be no exception. // unacknowledged stanza queue. There can be no exception.
assert(ackedStanza != null); if (ackedStanza == null) {
throw new StreamManagementCounterError(handledCount, serverHandledStanzasCount,
ackedStanzasCount, ackedStanzas);
}
ackedStanzas.add(ackedStanza); ackedStanzas.add(ackedStanza);
} }
@ -1691,7 +1726,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
String id = ackedStanza.getStanzaId(); String id = ackedStanza.getStanzaId();
if (StringUtils.isNullOrEmpty(id)) { if (StringUtils.isNullOrEmpty(id)) {
return; continue;
} }
PacketListener listener = stanzaIdAcknowledgedListeners.remove(id); PacketListener listener = stanzaIdAcknowledgedListeners.remove(id);
if (listener != null) { if (listener != null) {
@ -1709,4 +1744,31 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
serverHandledStanzasCount = handledCount; serverHandledStanzasCount = handledCount;
} }
/**
* Set the default bundle and defer callback used for new connections.
*
* @param defaultBundleAndDeferCallback
* @see BundleAndDeferCallback
* @since 4.1
*/
public static void setDefaultBundleAndDeferCallback(BundleAndDeferCallback defaultBundleAndDeferCallback) {
XMPPTCPConnection.defaultBundleAndDeferCallback = defaultBundleAndDeferCallback;
}
/**
* Set the bundle and defer callback used for this connection.
* <p>
* You can use <code>null</code> as argument to reset the callback. Outgoing stanzas will then
* no longer get deferred.
* </p>
*
* @param bundleAndDeferCallback the callback or <code>null</code>.
* @see BundleAndDeferCallback
* @since 4.1
*/
public void setBundleandDeferCallback(BundleAndDeferCallback bundleAndDeferCallback) {
this.bundleAndDeferCallback = bundleAndDeferCallback;
}
} }