mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-09-10 09:39:39 +02:00
Add IQ request handler API
This also moves the logic to send error IQ replies from "when there is no IQ provider registerd" to "when there is no IQ request handler registered". Which has for example the advantage that IQ parsing no longer asks for a connection instance.
This commit is contained in:
parent
fcb4844d10
commit
bb8dcc9874
28 changed files with 533 additions and 317 deletions
|
@ -58,7 +58,9 @@ import org.jivesoftware.smack.debugger.SmackDebugger;
|
|||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.filter.PacketIDFilter;
|
||||
import org.jivesoftware.smack.iqrequest.IQRequestHandler;
|
||||
import org.jivesoftware.smack.packet.Bind;
|
||||
import org.jivesoftware.smack.packet.ErrorIQ;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Mechanisms;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
|
@ -68,6 +70,7 @@ import org.jivesoftware.smack.packet.RosterVer;
|
|||
import org.jivesoftware.smack.packet.Session;
|
||||
import org.jivesoftware.smack.packet.StartTls;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
|
||||
import org.jivesoftware.smack.parsing.UnparsablePacket;
|
||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||
|
@ -279,6 +282,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
protected boolean wasAuthenticated = false;
|
||||
|
||||
private final Map<String, IQRequestHandler> setIqRequestHandler = new HashMap<>();
|
||||
private final Map<String, IQRequestHandler> getIqRequestHandler = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new XMPPConnection to a XMPP server.
|
||||
*
|
||||
|
@ -907,7 +913,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
int parserDepth = parser.getDepth();
|
||||
Packet stanza = null;
|
||||
try {
|
||||
stanza = PacketParserUtils.parseStanza(parser, this);
|
||||
stanza = PacketParserUtils.parseStanza(parser);
|
||||
}
|
||||
catch (Exception e) {
|
||||
CharSequence content = PacketParserUtils.parseContentDepth(parser,
|
||||
|
@ -962,6 +968,81 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
* @param packet the packet to notify the PacketCollectors and receive listeners about.
|
||||
*/
|
||||
protected void invokePacketCollectorsAndNotifyRecvListeners(final Packet packet) {
|
||||
if (packet instanceof IQ) {
|
||||
final IQ iq = (IQ) packet;
|
||||
final IQ.Type type = iq.getType();
|
||||
switch (type) {
|
||||
case set:
|
||||
case get:
|
||||
final String key = XmppStringUtils.generateKey(iq.getChildElementName(), iq.getChildElementNamespace());
|
||||
IQRequestHandler iqRequestHandler = null;
|
||||
switch (type) {
|
||||
case set:
|
||||
synchronized (setIqRequestHandler) {
|
||||
iqRequestHandler = setIqRequestHandler.get(key);
|
||||
}
|
||||
break;
|
||||
case get:
|
||||
synchronized (getIqRequestHandler) {
|
||||
iqRequestHandler = getIqRequestHandler.get(key);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Should only encounter IQ type 'get' or 'set'");
|
||||
}
|
||||
if (iqRequestHandler == null) {
|
||||
// If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an
|
||||
// IQ of type "error" with code 501 ("feature-not-implemented")
|
||||
ErrorIQ errorIQ = IQ.createErrorResponse(iq, new XMPPError(
|
||||
XMPPError.Condition.feature_not_implemented));
|
||||
try {
|
||||
sendPacket(errorIQ);
|
||||
}
|
||||
catch (NotConnectedException e) {
|
||||
LOGGER.log(Level.WARNING, "NotConnectedException while sending error IQ to unkown IQ request", e);
|
||||
}
|
||||
} else {
|
||||
ExecutorService executorService = null;
|
||||
switch (iqRequestHandler.getMode()) {
|
||||
case sync:
|
||||
executorService = singleThreadedExecutorService;
|
||||
break;
|
||||
case async:
|
||||
executorService = cachedExecutorService;
|
||||
break;
|
||||
}
|
||||
final IQRequestHandler finalIqRequestHandler = iqRequestHandler;
|
||||
executorService.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
IQ response = finalIqRequestHandler.handleIQRequest(iq);
|
||||
if (response == null) {
|
||||
// It is not ideal if the IQ request handler does not return an IQ response, because RFC
|
||||
// 6120 § 8.1.2 does specify that a response is mandatory. But some APIs, mostly the
|
||||
// file transfer one, does not always return a result, so we need to handle this case
|
||||
// gracefully for now
|
||||
// TODO Add a warning if response is null once all APIs using handleIQRequest return an
|
||||
// IQ response. Later consider throwing an IllegalStateException
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sendPacket(response);
|
||||
}
|
||||
catch (NotConnectedException e) {
|
||||
LOGGER.log(Level.WARNING, "NotConnectedException while sending response to IQ request", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The following returns makes it impossible for packet listeners and collectors to
|
||||
// filter for IQ request stanzas, i.e. IQs of type 'set' or 'get'. This is the
|
||||
// desired behavior.
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// First handle the async recv listeners. Note that this code is very similar to what follows a few lines below,
|
||||
// the only difference is that asyncRecvListeners is used here and that the packet listeners are started in
|
||||
// their own thread.
|
||||
|
@ -1398,6 +1479,46 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
}, getPacketReplyTimeout(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQRequestHandler registerIQRequestHandler(final IQRequestHandler iqRequestHandler) {
|
||||
final String key = XmppStringUtils.generateKey(iqRequestHandler.getElement(), iqRequestHandler.getNamespace());
|
||||
switch (iqRequestHandler.getType()) {
|
||||
case set:
|
||||
synchronized (setIqRequestHandler) {
|
||||
return setIqRequestHandler.put(key, iqRequestHandler);
|
||||
}
|
||||
case get:
|
||||
synchronized (getIqRequestHandler) {
|
||||
return getIqRequestHandler.put(key, iqRequestHandler);
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Only IQ type of 'get' and 'set' allowed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IQRequestHandler unregisterIQRequestHandler(IQRequestHandler iqRequestHandler) {
|
||||
return unregisterIQRequestHandler(iqRequestHandler.getElement(), iqRequestHandler.getNamespace(),
|
||||
iqRequestHandler.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQRequestHandler unregisterIQRequestHandler(String element, String namespace, IQ.Type type) {
|
||||
final String key = XmppStringUtils.generateKey(element, namespace);
|
||||
switch (type) {
|
||||
case set:
|
||||
synchronized (setIqRequestHandler) {
|
||||
return setIqRequestHandler.remove(key);
|
||||
}
|
||||
case get:
|
||||
synchronized (getIqRequestHandler) {
|
||||
return getIqRequestHandler.remove(key);
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Only IQ type of 'get' and 'set' allowed");
|
||||
}
|
||||
}
|
||||
|
||||
private long lastStanzaReceived;
|
||||
|
||||
public long getLastStanzaReceived() {
|
||||
|
|
|
@ -36,16 +36,18 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
|
|||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
import org.jivesoftware.smack.filter.IQTypeFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.RosterPacket;
|
||||
import org.jivesoftware.smack.packet.RosterVer;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.packet.XMPPError.Condition;
|
||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
||||
|
@ -67,9 +69,6 @@ public class Roster {
|
|||
|
||||
private static final Logger LOGGER = Logger.getLogger(Roster.class.getName());
|
||||
|
||||
private static final PacketFilter ROSTER_PUSH_FILTER = new AndFilter(new PacketTypeFilter(
|
||||
RosterPacket.class), IQTypeFilter.SET);
|
||||
|
||||
private static final PacketFilter PRESENCE_PACKET_FILTER = new PacketTypeFilter(Presence.class);
|
||||
|
||||
/**
|
||||
|
@ -130,7 +129,7 @@ public class Roster {
|
|||
// Note that we use sync packet listeners because RosterListeners should be invoked in the same order as the
|
||||
// roster stanzas arrive.
|
||||
// Listen for any roster packets.
|
||||
connection.addSyncPacketListener(new RosterPushListener(), ROSTER_PUSH_FILTER);
|
||||
connection.registerIQRequestHandler(new RosterPushListener());
|
||||
// Listen for any presence packets.
|
||||
connection.addSyncPacketListener(presencePacketListener, PRESENCE_PACKET_FILTER);
|
||||
|
||||
|
@ -1118,10 +1117,15 @@ public class Roster {
|
|||
/**
|
||||
* Listens for all roster pushes and processes them.
|
||||
*/
|
||||
private class RosterPushListener implements PacketListener {
|
||||
private class RosterPushListener extends AbstractIqRequestHandler {
|
||||
|
||||
public void processPacket(Packet packet) throws NotConnectedException {
|
||||
RosterPacket rosterPacket = (RosterPacket) packet;
|
||||
private RosterPushListener() {
|
||||
super(RosterPacket.ELEMENT, RosterPacket.NAMESPACE, Type.set, Mode.sync);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQ handleIQRequest(IQ iqRequest) {
|
||||
RosterPacket rosterPacket = (RosterPacket) iqRequest;
|
||||
|
||||
// Roster push (RFC 6121, 2.1.6)
|
||||
// A roster push with a non-empty from not matching our address MUST be ignored
|
||||
|
@ -1130,14 +1134,14 @@ public class Roster {
|
|||
if (from != null && !from.equals(jid)) {
|
||||
LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + jid + "' from='" + from
|
||||
+ "'");
|
||||
return;
|
||||
return IQ.createErrorResponse(iqRequest, new XMPPError(Condition.service_unavailable));
|
||||
}
|
||||
|
||||
// A roster push must contain exactly one entry
|
||||
Collection<Item> items = rosterPacket.getRosterItems();
|
||||
if (items.size() != 1) {
|
||||
LOGGER.warning("Ignoring roster push with not exaclty one entry. size=" + items.size());
|
||||
return;
|
||||
return IQ.createErrorResponse(iqRequest, new XMPPError(Condition.bad_request));
|
||||
}
|
||||
|
||||
Collection<String> addedEntries = new ArrayList<String>();
|
||||
|
@ -1164,12 +1168,13 @@ public class Roster {
|
|||
rosterStore.addEntry(item, version);
|
||||
}
|
||||
}
|
||||
connection.sendPacket(IQ.createResultIQ(rosterPacket));
|
||||
|
||||
removeEmptyGroups();
|
||||
|
||||
// Fire event for roster listeners.
|
||||
fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);
|
||||
|
||||
return IQ.createResultIQ(rosterPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
|
|||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.iqrequest.IQRequestHandler;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
|
@ -565,6 +566,34 @@ public interface XMPPConnection {
|
|||
*/
|
||||
public void addOneTimeSyncCallback(PacketListener callback, PacketFilter packetFilter);
|
||||
|
||||
/**
|
||||
* Register an IQ request handler with this connection.
|
||||
* <p>
|
||||
* IQ request handler process incoming IQ requests, i.e. incoming IQ stanzas of type 'get' or 'set', and return a result.
|
||||
* </p>
|
||||
* @param iqRequestHandler the IQ request handler to register.
|
||||
* @return the previously registered IQ request handler or null.
|
||||
*/
|
||||
public IQRequestHandler registerIQRequestHandler(IQRequestHandler iqRequestHandler);
|
||||
|
||||
/**
|
||||
* Convenience method for {@link #unregisterIQRequestHandler(String, String, org.jivesoftware.smack.packet.IQ.Type)}.
|
||||
*
|
||||
* @param iqRequestHandler
|
||||
* @return the previously registered IQ request handler or null.
|
||||
*/
|
||||
public IQRequestHandler unregisterIQRequestHandler(IQRequestHandler iqRequestHandler);
|
||||
|
||||
/**
|
||||
* Unregister an IQ request handler with this connection.
|
||||
*
|
||||
* @param element the IQ element the IQ request handler is responsible for.
|
||||
* @param namespace the IQ namespace the IQ request handler is responsible for.
|
||||
* @param type the IQ type the IQ request handler is responsible for.
|
||||
* @return the previously registered IQ request handler or null.
|
||||
*/
|
||||
public IQRequestHandler unregisterIQRequestHandler(String element, String namespace, IQ.Type type);
|
||||
|
||||
/**
|
||||
* Returns the timestamp in milliseconds when the last stanza was received.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
*
|
||||
* 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.iqrequest;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
|
||||
public abstract class AbstractIqRequestHandler implements IQRequestHandler {
|
||||
|
||||
private final String element;
|
||||
private final String namespace;
|
||||
private final Type type;
|
||||
private final Mode mode;
|
||||
|
||||
protected AbstractIqRequestHandler(String element, String namespace, Type type, Mode mode) {
|
||||
switch (type) {
|
||||
case set:
|
||||
case get:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Only get and set IQ type allowed");
|
||||
}
|
||||
this.element = element;
|
||||
this.namespace = namespace;
|
||||
this.type = type;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract IQ handleIQRequest(IQ iqRequest);
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
*
|
||||
* 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.iqrequest;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
|
||||
public interface IQRequestHandler {
|
||||
|
||||
public enum Mode {
|
||||
/**
|
||||
* Process requests synchronously, i.e. in the order they arrive. Uses a single thread, which means that the other
|
||||
* requests have to wait until all previous synchronous requests have been handled. Use {@link #async} if
|
||||
* possible for performance reasons.
|
||||
*/
|
||||
sync,
|
||||
|
||||
/**
|
||||
* Process IQ requests asynchronously, i.e. concurrent. This does not guarantee that requests are processed in the
|
||||
* same order they arrive.
|
||||
*/
|
||||
async,
|
||||
}
|
||||
|
||||
public IQ handleIQRequest(IQ iqRequest);
|
||||
|
||||
public Mode getMode();
|
||||
|
||||
public IQ.Type getType();
|
||||
|
||||
public String getElement();
|
||||
|
||||
public String getNamespace();
|
||||
}
|
|
@ -35,4 +35,5 @@ public class ErrorIQ extends SimpleIQ {
|
|||
type = IQ.Type.error;
|
||||
setError(xmppError);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -224,12 +224,12 @@ public abstract class IQ extends Packet {
|
|||
* {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
|
||||
* @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
|
||||
*/
|
||||
public static IQ createErrorResponse(final IQ request, final XMPPError error) {
|
||||
public static ErrorIQ createErrorResponse(final IQ request, final XMPPError error) {
|
||||
if (!(request.getType() == Type.get || request.getType() == Type.set)) {
|
||||
throw new IllegalArgumentException(
|
||||
"IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
|
||||
}
|
||||
final IQ result = new ErrorIQ(error);
|
||||
final ErrorIQ result = new ErrorIQ(error);
|
||||
result.setPacketID(request.getPacketID());
|
||||
result.setFrom(request.getTo());
|
||||
result.setTo(request.getFrom());
|
||||
|
|
|
@ -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.packet;
|
||||
|
||||
/**
|
||||
* An IQ stanzas that could not be parsed because no provider was found.
|
||||
*/
|
||||
public class UnparsedIQ extends IQ {
|
||||
|
||||
public UnparsedIQ(String element, String namespace, CharSequence content) {
|
||||
super(element, namespace);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
private final CharSequence content;
|
||||
|
||||
public CharSequence getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.compress.packet.Compress;
|
||||
import org.jivesoftware.smack.packet.DefaultPacketExtension;
|
||||
import org.jivesoftware.smack.packet.EmptyResultIQ;
|
||||
|
@ -42,6 +41,7 @@ import org.jivesoftware.smack.packet.Presence;
|
|||
import org.jivesoftware.smack.packet.Session;
|
||||
import org.jivesoftware.smack.packet.StartTls;
|
||||
import org.jivesoftware.smack.packet.StreamError;
|
||||
import org.jivesoftware.smack.packet.UnparsedIQ;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.provider.IQProvider;
|
||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||
|
@ -128,32 +128,29 @@ public class PacketParserUtils {
|
|||
return parser;
|
||||
}
|
||||
|
||||
public static Packet parseStanza(String stanza) throws Exception {
|
||||
public static Packet parseStanza(String stanza) throws XmlPullParserException, IOException, SmackException {
|
||||
return parseStanza(getParserFor(stanza));
|
||||
}
|
||||
|
||||
public static Packet parseStanza(XmlPullParser parser) throws Exception {
|
||||
return parseStanza(parser, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse and return either a Message, IQ or Presence stanza.
|
||||
*
|
||||
* connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas.
|
||||
*
|
||||
* @param parser
|
||||
* @param connection
|
||||
* @return a packet which is either a Message, IQ or Presence.
|
||||
* @throws Exception
|
||||
* @throws XmlPullParserException
|
||||
* @throws SmackException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Packet parseStanza(XmlPullParser parser, XMPPConnection connection) throws Exception {
|
||||
public static Packet parseStanza(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException {
|
||||
ParserUtils.assertAtStartTag(parser);
|
||||
final String name = parser.getName();
|
||||
switch (name) {
|
||||
case Message.ELEMENT:
|
||||
return parseMessage(parser);
|
||||
case IQ.ELEMENT:
|
||||
return parse(parser, connection);
|
||||
return parseIQ(parser);
|
||||
case Presence.ELEMENT:
|
||||
return parsePresence(parser);
|
||||
default:
|
||||
|
@ -597,11 +594,12 @@ public class PacketParserUtils {
|
|||
* Parses an IQ packet.
|
||||
*
|
||||
* @param parser the XML parser, positioned at the start of an IQ packet.
|
||||
* @param connection the optional XMPPConnection used to send feature-not-implemented replies.
|
||||
* @return an IQ object.
|
||||
* @throws Exception if an exception occurs while parsing the packet.
|
||||
* @throws XmlPullParserException
|
||||
* @throws IOException
|
||||
* @throws SmackException
|
||||
*/
|
||||
public static IQ parse(XmlPullParser parser, XMPPConnection connection) throws Exception {
|
||||
public static IQ parseIQ(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException {
|
||||
ParserUtils.assertAtStartTag(parser);
|
||||
final int initialDepth = parser.getDepth();
|
||||
IQ iqPacket = null;
|
||||
|
@ -630,14 +628,12 @@ public class PacketParserUtils {
|
|||
if (provider != null) {
|
||||
iqPacket = provider.parse(parser);
|
||||
}
|
||||
// Only handle unknown IQs of type result. Types of 'get' and 'set' which are not understood
|
||||
// have to be answered with an IQ error response. See the code a few lines below
|
||||
// Note that if we reach this code, it is guranteed that the result IQ contained a child element
|
||||
// (RFC 6120 § 8.2.3 6) because otherwhise we would have reached the END_TAG first.
|
||||
else if (IQ.Type.result == type) {
|
||||
else {
|
||||
// No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
|
||||
// so that the content of the IQ can be examined later on
|
||||
iqPacket = new UnparsedResultIQ(elementName, namespace, parseElement(parser));
|
||||
iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -652,20 +648,6 @@ public class PacketParserUtils {
|
|||
// Decide what to do when an IQ packet was not understood
|
||||
if (iqPacket == null) {
|
||||
switch (type) {
|
||||
case get:
|
||||
case set:
|
||||
if (connection == null) {
|
||||
return null;
|
||||
}
|
||||
// If the IQ stanza is of type "get" or "set" containing a child element qualified
|
||||
// by a namespace with no registered Smack provider, then answer an IQ of type
|
||||
// "error" with code 501 ("feature-not-implemented")
|
||||
iqPacket = new ErrorIQ(new XMPPError(XMPPError.Condition.feature_not_implemented));
|
||||
iqPacket.setPacketID(id);
|
||||
iqPacket.setTo(from);
|
||||
iqPacket.setFrom(to);
|
||||
connection.sendPacket(iqPacket);
|
||||
return null;
|
||||
case error:
|
||||
// If an IQ packet wasn't created above, create an empty error IQ packet.
|
||||
iqPacket = new ErrorIQ(error);
|
||||
|
@ -673,6 +655,8 @@ public class PacketParserUtils {
|
|||
case result:
|
||||
iqPacket = new EmptyResultIQ();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1044,28 +1028,4 @@ public class PacketParserUtils {
|
|||
collection.add(packetExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents and unparsed IQ of the type 'result'. Usually it's created when no IQProvider
|
||||
* was found for the IQ element.
|
||||
*
|
||||
* The child elements can be examined with the getChildElementXML() method.
|
||||
*
|
||||
*/
|
||||
public static class UnparsedResultIQ extends IQ {
|
||||
private UnparsedResultIQ(String element, String namespace, CharSequence content) {
|
||||
super(element, namespace);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
private final CharSequence content;
|
||||
|
||||
public CharSequence getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,11 +184,9 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
* and that has not been returned by earlier calls to this method.
|
||||
*
|
||||
* @return a sent packet.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <P extends TopLevelStreamElement> P getSentPacket() throws InterruptedException {
|
||||
return (P) queue.poll(5, TimeUnit.MINUTES);
|
||||
public <P extends TopLevelStreamElement> P getSentPacket() {
|
||||
return getSentPacket(5 * 60);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,11 +196,16 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
* have been sent yet.
|
||||
*
|
||||
* @return a sent packet.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <P extends TopLevelStreamElement> P getSentPacket(int wait) throws InterruptedException {
|
||||
return (P) queue.poll(wait, TimeUnit.SECONDS);
|
||||
public <P extends TopLevelStreamElement> P getSentPacket(int wait) {
|
||||
try {
|
||||
return (P) queue.poll(wait, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// TODO handle spurious interrupts
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.jivesoftware.smack;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
@ -29,6 +30,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.jivesoftware.smack.packet.ErrorIQ;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
|
@ -36,6 +38,7 @@ import org.jivesoftware.smack.packet.RosterPacket;
|
|||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.ItemType;
|
||||
import org.jivesoftware.smack.packet.XMPPError.Condition;
|
||||
import org.jivesoftware.smack.test.util.TestUtils;
|
||||
import org.jivesoftware.smack.test.util.WaitForPacketListener;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
|
@ -329,7 +332,7 @@ public class RosterTest {
|
|||
.append("</query>")
|
||||
.append("</iq>");
|
||||
final XmlPullParser parser = TestUtils.getIQParser(sb.toString());
|
||||
final IQ rosterPush = PacketParserUtils.parse(parser, connection);
|
||||
final IQ rosterPush = PacketParserUtils.parseIQ(parser);
|
||||
initRoster();
|
||||
rosterListener.reset();
|
||||
|
||||
|
@ -369,11 +372,14 @@ public class RosterTest {
|
|||
packet.setFrom("mallory@example.com");
|
||||
packet.addRosterItem(new Item("spam@example.com", "Cool products!"));
|
||||
|
||||
WaitForPacketListener waitForPacketListener = new WaitForPacketListener();
|
||||
connection.addAsyncPacketListener(waitForPacketListener, null);
|
||||
final String requestId = packet.getPacketID();
|
||||
// Simulate receiving the roster push
|
||||
connection.processPacket(packet);
|
||||
waitForPacketListener.waitUntilInvocationOrTimeout();
|
||||
|
||||
// Smack should reply with an error IQ
|
||||
ErrorIQ errorIQ = (ErrorIQ) connection.getSentPacket();
|
||||
assertEquals(requestId, errorIQ.getPacketID());
|
||||
assertEquals(Condition.service_unavailable, errorIQ.getError().getCondition());
|
||||
|
||||
assertNull("Contact was added to roster", connection.getRoster().getEntry("spam@example.com"));
|
||||
}
|
||||
|
@ -465,7 +471,7 @@ public class RosterTest {
|
|||
.append("</query>")
|
||||
.append("</iq>");
|
||||
final XmlPullParser parser = TestUtils.getIQParser(sb.toString());
|
||||
final IQ rosterPush = PacketParserUtils.parse(parser, connection);
|
||||
final IQ rosterPush = PacketParserUtils.parseIQ(parser);
|
||||
initRoster();
|
||||
rosterListener.reset();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue