diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/com/isode/stroke/disco/CapsInfoGenerator.java | 2 | ||||
| -rw-r--r-- | src/com/isode/stroke/disco/ClientDiscoManager.java | 2 | ||||
| -rw-r--r-- | src/com/isode/stroke/disco/DiscoServiceWalker.java | 221 | ||||
| -rw-r--r-- | src/com/isode/stroke/disco/EntityCapsManager.java | 51 | ||||
| -rw-r--r-- | src/com/isode/stroke/disco/EntityCapsProvider.java | 5 | ||||
| -rw-r--r-- | src/com/isode/stroke/disco/FeatureOracle.java | 115 | ||||
| -rw-r--r-- | src/com/isode/stroke/disco/JIDDiscoInfoResponder.java | 72 | ||||
| -rw-r--r-- | src/com/isode/stroke/elements/IQ.java | 52 | ||||
| -rw-r--r-- | src/com/isode/stroke/queries/Request.java | 114 | 
9 files changed, 559 insertions, 75 deletions
diff --git a/src/com/isode/stroke/disco/CapsInfoGenerator.java b/src/com/isode/stroke/disco/CapsInfoGenerator.java index 2bbd843..e7801fa 100644 --- a/src/com/isode/stroke/disco/CapsInfoGenerator.java +++ b/src/com/isode/stroke/disco/CapsInfoGenerator.java @@ -17,7 +17,7 @@ import com.isode.stroke.elements.FormField;  import com.isode.stroke.stringcodecs.Base64;  public class CapsInfoGenerator { -    private String node_; +    private String node_ = "";      private CryptoProvider crypto_;      private final static Comparator<FormField> compareFields = new Comparator<FormField>() { diff --git a/src/com/isode/stroke/disco/ClientDiscoManager.java b/src/com/isode/stroke/disco/ClientDiscoManager.java index 3770fbc..ba620ed 100644 --- a/src/com/isode/stroke/disco/ClientDiscoManager.java +++ b/src/com/isode/stroke/disco/ClientDiscoManager.java @@ -15,7 +15,7 @@ public class ClientDiscoManager {      private PayloadAddingPresenceSender presenceSender;      private CryptoProvider crypto;      private DiscoInfoResponder discoInfoResponder; -    private String capsNode; +    private String capsNode = "";      private CapsInfo capsInfo;      /** diff --git a/src/com/isode/stroke/disco/DiscoServiceWalker.java b/src/com/isode/stroke/disco/DiscoServiceWalker.java new file mode 100644 index 0000000..1b5c54e --- /dev/null +++ b/src/com/isode/stroke/disco/DiscoServiceWalker.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.disco; + +import com.isode.stroke.jid.JID; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.DiscoItems; +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.disco.GetDiscoInfoRequest; +import com.isode.stroke.disco.GetDiscoItemsRequest; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Signal; +import com.isode.stroke.signals.Slot2; +import com.isode.stroke.signals.Signal2; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.base.NotNull; +import java.util.logging.Logger; +import java.util.HashSet; +import java.util.Set; +import com.isode.stroke.base.NotNull; + +/** + * Recursively walk service discovery trees to find all services offered. + * This stops on any disco item that's not reporting itself as a server. + */ +public class DiscoServiceWalker { + +	private JID service_; +	private IQRouter iqRouter_; +	private long maxSteps_; +	private boolean active_; +	private Set<JID> servicesBeingSearched_ = new HashSet<JID>(); +	private Set<JID> searchedServices_ = new HashSet<JID>(); +	private Set<GetDiscoInfoRequest> pendingDiscoInfoRequests_ = new HashSet<GetDiscoInfoRequest>(); +	private Set<GetDiscoItemsRequest> pendingDiscoItemsRequests_ = new HashSet<GetDiscoItemsRequest>(); +	private Logger logger_ = Logger.getLogger(this.getClass().getName()); +	private SignalConnection onServiceFoundConnection; +	private SignalConnection onWalkAbortedConnection; +	private SignalConnection onWalkCompleteConnection; +	private SignalConnection onResponseDiscoInfoConnection; +	private SignalConnection onResponseDiscoItemsConnection; + +	/** Emitted for each service found. */ +	public final Signal2<JID, DiscoInfo> onServiceFound = new Signal2<JID, DiscoInfo>(); + +	/** Emitted when walking is aborted. */ +	public final Signal onWalkAborted = new Signal(); + +	/** Emitted when walking is complete.*/ +	public final Signal onWalkComplete = new Signal(); + +	/** +	* Parameterized Constructor. +	* @param service, Not Null. +	* @param iqRouter, Not Null. +	*/ +	public DiscoServiceWalker(JID service, IQRouter iqRouter) { +		this(service, iqRouter, 200); +	} + +	/** +	* Parameterized Constructor. +	* @param service, Not Null. +	* @param iqRouter, Not Null. +	* @param maxSteps. +	*/ +	public DiscoServiceWalker(JID service, IQRouter iqRouter, long maxSteps) { +		NotNull.exceptIfNull(service, "service"); +		NotNull.exceptIfNull(iqRouter, "iqRouter"); +		this.service_ = service; +		this.iqRouter_ = iqRouter; +		this.maxSteps_ = maxSteps; +		this.active_ = false; +	} + +	/** +	 * Start the walk. +	 * +	 * Call this exactly once. +	 */ +	public void beginWalk() { +		logger_.fine("Starting walk to " + service_ + "\n"); +		assert(!active_); +		assert(servicesBeingSearched_.isEmpty()); +		active_ = true; +		walkNode(service_); +	} + +	/** +	 * End the walk. +	 */ +	public void endWalk() { +		if (active_) { +			logger_.fine("Ending walk to" + service_ + "\n"); +			for (GetDiscoInfoRequest request : pendingDiscoInfoRequests_) { +				onResponseDiscoInfoConnection.disconnect(); +			} +			for (GetDiscoItemsRequest request : pendingDiscoItemsRequests_) { +				onResponseDiscoItemsConnection.disconnect(); +			} +			active_ = false; +			onWalkAborted.emit(); +		}		 +	} + +	public boolean isActive() { +		return active_; +	} + +	private void walkNode(JID jid) { +		logger_.fine("Walking node" + jid + "\n"); +		servicesBeingSearched_.add(jid); +		searchedServices_.add(jid); +		final GetDiscoInfoRequest discoInfoRequest = GetDiscoInfoRequest.create(jid, iqRouter_); +		onResponseDiscoInfoConnection = discoInfoRequest.onResponse.connect(new Slot2<DiscoInfo, ErrorPayload>() { + +			@Override +			public void call(DiscoInfo info, ErrorPayload error) { +				handleDiscoInfoResponse(info, error, discoInfoRequest); +			} +		}); +		pendingDiscoInfoRequests_.add(discoInfoRequest); +		discoInfoRequest.send(); +	} + +	private void markNodeCompleted(JID jid) { +		logger_.fine("Node completed " + jid + "\n"); +		servicesBeingSearched_.remove(jid); +		/* All results are in */ +		if (servicesBeingSearched_.isEmpty()) { +			active_ = false; +			onWalkComplete.emit(); +		} +		/* Check if we're on a rampage */ +		else if (searchedServices_.size() >= maxSteps_) { +			active_ = false; +			onWalkComplete.emit(); +		} +	} + +	private void handleDiscoInfoResponse(DiscoInfo info, ErrorPayload error, GetDiscoInfoRequest request) { +		/* If we got canceled, don't do anything */ +		if (!active_) { +			return; +		} + +		logger_.fine("Disco info response from " + request.getReceiver() + "\n"); + +		pendingDiscoInfoRequests_.remove(request); +		if (error != null) { +			handleDiscoError(request.getReceiver(), error); +			return; +		} + +		boolean couldContainServices = false; +		for (DiscoInfo.Identity identity : info.getIdentities()) { +			if (identity.getCategory().equals("server")) { +				couldContainServices = true; +			} +		} +		boolean completed = false; +		if (couldContainServices) { +			final GetDiscoItemsRequest discoItemsRequest = GetDiscoItemsRequest.create(request.getReceiver(), iqRouter_); +			onResponseDiscoItemsConnection = discoItemsRequest.onResponse.connect(new Slot2<DiscoItems, ErrorPayload>() { + +				@Override +				public void call(DiscoItems item, ErrorPayload error) { +					handleDiscoItemsResponse(item, error, discoItemsRequest); +				} +			}); +			pendingDiscoItemsRequests_.add(discoItemsRequest); +			discoItemsRequest.send(); +		} else { +			completed = true; +		} +		onServiceFound.emit(request.getReceiver(), info); +		if (completed) { +			markNodeCompleted(request.getReceiver()); +		}		 +	} + +	private void handleDiscoItemsResponse(DiscoItems items, ErrorPayload error, GetDiscoItemsRequest request) { +		/* If we got canceled, don't do anything */ +		if (!active_) { +			return; +		} + +		logger_.fine("Received disco items from " + request.getReceiver() + "\n"); +		pendingDiscoItemsRequests_.remove(request); +		if (error != null) { +			handleDiscoError(request.getReceiver(), error); +			return; +		} +		for (DiscoItems.Item item : items.getItems()) { +			if (item.getNode().isEmpty()) { +				/* Don't look at noded items. It's possible that this will exclude some services, +				 * but I've never seen one in the wild, and it's an easy fix for not looping. +				 */ +				if(!searchedServices_.contains(item.getJID())) { +					logger_.fine("Received disco item " + item.getJID() + "\n"); +					walkNode(item.getJID()); +				} +			} +		} +		markNodeCompleted(request.getReceiver()); +	} + +	private void handleDiscoError(JID jid, ErrorPayload error) { +		logger_.fine("Disco error from " + jid + "\n"); +		markNodeCompleted(jid); +	} +}
\ No newline at end of file diff --git a/src/com/isode/stroke/disco/EntityCapsManager.java b/src/com/isode/stroke/disco/EntityCapsManager.java index a41ec11..6fb201c 100644 --- a/src/com/isode/stroke/disco/EntityCapsManager.java +++ b/src/com/isode/stroke/disco/EntityCapsManager.java @@ -4,7 +4,7 @@   */  package com.isode.stroke.disco; -import java.util.HashMap; +import java.util.TreeMap;  import java.util.Map;  import com.isode.stroke.client.StanzaChannel; @@ -17,11 +17,10 @@ import com.isode.stroke.signals.Slot1;  public class EntityCapsManager extends EntityCapsProvider {      private final CapsProvider capsProvider; -    private final Map<JID, String> caps = new HashMap<JID, String>(); +    private final Map<JID, String> caps = new TreeMap<JID, String>();      public EntityCapsManager(CapsProvider capsProvider, StanzaChannel stanzaChannel) {          this.capsProvider = capsProvider; -          stanzaChannel.onPresenceReceived.connect(new Slot1<Presence>() {              @Override              public void call(Presence p1) { @@ -43,28 +42,28 @@ public class EntityCapsManager extends EntityCapsProvider {      }      private void handlePresenceReceived(Presence presence) { -	    JID from = presence.getFrom(); -	    if (presence.isAvailable()) { -	        CapsInfo capsInfo = presence.getPayload(new CapsInfo()); -	        if (capsInfo == null || !capsInfo.getHash().equals("sha-1") || presence.getPayload(new ErrorPayload()) != null) { -	            return; -	        } -	        String hash = capsInfo.getVersion(); -	        String i = caps.get(from); -	        if (!hash.equals(i)) { -	            caps.put(from, hash); -	            DiscoInfo disco = capsProvider.getCaps(hash); -	            if (disco != null || i != null) { -	                onCapsChanged.emit(from); -	            } -	        } -	    } -	    else { -	        if (caps.remove(from) != null) { -	            onCapsChanged.emit(from); -	        } -	    } -	} +        JID from = presence.getFrom(); +        if (presence.isAvailable()) { +            CapsInfo capsInfo = presence.getPayload(new CapsInfo()); +            if (capsInfo == null || !capsInfo.getHash().equals("sha-1") || presence.getPayload(new ErrorPayload()) != null) { +                return; +            } +            String hash = capsInfo.getVersion(); +            String i = caps.get(from); +            if (!hash.equals(i)) { +                caps.put(from, hash); +                DiscoInfo disco = capsProvider.getCaps(hash); +                if (disco != null || i != null) { +                    onCapsChanged.emit(from); +                } +            } +        } +        else { +            if (caps.remove(from) != null) { +                onCapsChanged.emit(from); +            } +        } +    }      private void handleStanzaChannelAvailableChanged(boolean available) {          if (available) { @@ -88,6 +87,6 @@ public class EntityCapsManager extends EntityCapsProvider {          if (caps.containsKey(jid)) {              return capsProvider.getCaps(caps.get(jid));          } -        return new DiscoInfo(); +        return null;      }  } diff --git a/src/com/isode/stroke/disco/EntityCapsProvider.java b/src/com/isode/stroke/disco/EntityCapsProvider.java index fd30173..4c0ddfa 100644 --- a/src/com/isode/stroke/disco/EntityCapsProvider.java +++ b/src/com/isode/stroke/disco/EntityCapsProvider.java @@ -8,6 +8,11 @@ import com.isode.stroke.elements.DiscoInfo;  import com.isode.stroke.jid.JID;  import com.isode.stroke.signals.Signal1; +/** + * This class provides information about capabilities of entities on the network. + * This information is provided in the form of service discovery + * information. + */  public abstract class EntityCapsProvider {  	/**  	 * Returns the service discovery information of the given JID. diff --git a/src/com/isode/stroke/disco/FeatureOracle.java b/src/com/isode/stroke/disco/FeatureOracle.java new file mode 100644 index 0000000..e01b2ab --- /dev/null +++ b/src/com/isode/stroke/disco/FeatureOracle.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.disco; + +import com.isode.stroke.base.Tristate; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.jid.JID; +import com.isode.stroke.disco.EntityCapsProvider; +import com.isode.stroke.presence.PresenceOracle; +//import com.isode.stroke.filetransfer.FileTransferManager; +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; + +public class FeatureOracle { + +	private EntityCapsProvider capsProvider_; +	private PresenceOracle presenceOracle_; + +	public FeatureOracle(EntityCapsProvider capsProvider, PresenceOracle presenceOracle) { +		this.capsProvider_ = capsProvider; +		this.presenceOracle_ = presenceOracle; +	} + +	/** +	* To PORT : FileTransfer. +	*/ +	/*public Tristate isFileTransferSupported(JID jid) { +		DiscoInfo discoInfo = getDiscoResultForJID(jid); +		if (discoInfo != null) { +			return FileTransferManager.isSupportedBy(discoInfo) ? Tristate.Yes : Tristate.No; +		} +		else { +			return Tristate.Maybe; +		} +	}*/ + +	public Tristate isMessageReceiptsSupported(JID jid) { +		return isFeatureSupported(jid, DiscoInfo.MessageDeliveryReceiptsFeature); +	} + +	public Tristate isMessageCorrectionSupported(JID jid) { +		return isFeatureSupported(jid, DiscoInfo.MessageCorrectionFeature); +	} + +	/** +	 * @brief getDiscoResultForJID returns a  shared reference to a DiscoInfo representing features supported by the jid. +	 * @param jid The JID to return the DiscoInfo for. +	 * @return DiscoResult. +	 */ +	private DiscoInfo getDiscoResultForJID(JID jid) { +		DiscoInfo discoInfo; +		if (jid.isBare()) { +			// Calculate the common subset of disco features of all available results and return that. +			Collection<Presence> availablePresences =  presenceOracle_.getAllPresence(jid); + +			boolean commonFeaturesInitialized = false; +			List<String> commonFeatures = new ArrayList<String>(); +			for(Presence presence : availablePresences) { +				DiscoInfo presenceDiscoInfo = capsProvider_.getCaps(presence.getFrom()); +				if (presenceDiscoInfo != null) { +					List<String> features = presenceDiscoInfo.getFeatures(); +					if (!commonFeaturesInitialized) { +						commonFeatures = features; +						commonFeaturesInitialized = true; +					} +					else { +						List<String> featuresToRemove = new ArrayList<String>(); +						for(String feature : commonFeatures) { +							if(!features.contains(feature)) { +								featuresToRemove.add(feature); +							} +						} +						for(String featureToRemove : featuresToRemove) { +							while(commonFeatures.contains(featureToRemove)) { +								commonFeatures.remove(featureToRemove); +							} +						} +					} +				} +			} +			discoInfo = new DiscoInfo(); + +			for(String commonFeature : commonFeatures) { +				discoInfo.addFeature(commonFeature); +			} +		} +		else { +			// Return the disco result of the full JID. +			discoInfo = capsProvider_.getCaps(jid); +		} + +		return discoInfo; +	} + +	private Tristate isFeatureSupported(JID jid, String feature) { +		DiscoInfo discoInfo = getDiscoResultForJID(jid); +		if (discoInfo != null) { +			return discoInfo.hasFeature(feature) ? Tristate.Yes : Tristate.No; +		} +		else { +			return Tristate.Maybe; +		} +	} +}
\ No newline at end of file diff --git a/src/com/isode/stroke/disco/JIDDiscoInfoResponder.java b/src/com/isode/stroke/disco/JIDDiscoInfoResponder.java new file mode 100644 index 0000000..f0c843b --- /dev/null +++ b/src/com/isode/stroke/disco/JIDDiscoInfoResponder.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.disco; + +import com.isode.stroke.jid.JID; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.queries.GetResponder; +import com.isode.stroke.queries.IQRouter; +import java.util.Map; +import java.util.HashMap; + +public class JIDDiscoInfoResponder extends GetResponder<DiscoInfo> { + +	private class JIDDiscoInfo { +		public DiscoInfo discoInfo; +		public Map<String, DiscoInfo> nodeDiscoInfo = new HashMap<String, DiscoInfo>(); +	} + +	private Map<JID, JIDDiscoInfo> info = new HashMap<JID, JIDDiscoInfo>(); + +	public JIDDiscoInfoResponder(IQRouter router) { +		super(new DiscoInfo(), router); +	} + +	public void clearDiscoInfo(JID jid) { +		info.remove(jid); +	} + +	public void setDiscoInfo(JID jid, DiscoInfo discoInfo) { +		JIDDiscoInfo jdisco = new JIDDiscoInfo(); +		jdisco.discoInfo = discoInfo; +		info.put(jid, jdisco); +	} + +	public void setDiscoInfo(JID jid, String node, DiscoInfo discoInfo) { +		DiscoInfo newInfo = discoInfo; +		newInfo.setNode(node); +		JIDDiscoInfo jdisco = new JIDDiscoInfo(); +		jdisco.nodeDiscoInfo.put(node, newInfo); +		info.put(jid, jdisco); +	} + +	protected boolean handleGetRequest(JID from, JID to, String id, DiscoInfo discoInfo) { +		if(info.containsKey(to)) { +			if (discoInfo.getNode().isEmpty()) { +				sendResponse(from, to, id, info.get(to).discoInfo); +			} +			else { +				if(info.get(to).nodeDiscoInfo.containsKey(discoInfo.getNode())) { +					sendResponse(from, to, id, info.get(to).nodeDiscoInfo.get(discoInfo.getNode())); +				} +				else { +					sendError(from, to, id, ErrorPayload.Condition.ItemNotFound, ErrorPayload.Type.Cancel); +				} +			} +		} +		else { +			sendError(from, to, id, ErrorPayload.Condition.ItemNotFound, ErrorPayload.Type.Cancel); +		} +		return true; +	} +}
\ No newline at end of file diff --git a/src/com/isode/stroke/elements/IQ.java b/src/com/isode/stroke/elements/IQ.java index d06d3a6..84645e4 100644 --- a/src/com/isode/stroke/elements/IQ.java +++ b/src/com/isode/stroke/elements/IQ.java @@ -33,35 +33,55 @@ public class IQ extends Stanza {      public static IQ createRequest(Type type, JID to, String id, Payload payload) {          IQ iq = new IQ(type); -        iq.setTo(to); +        if(to.isValid()) { +            iq.setTo(to);             +        }          iq.setID(id); -        iq.addPayload(payload); +        if(payload != null) { +            iq.addPayload(payload); +        }          return iq;      } +    public static IQ createResult(JID to, String id) { +        return createResult(to, id, null); +    } +      public static IQ createResult(JID to, String id, Payload payload) {          IQ iq = new IQ(Type.Result);          iq.setTo(to);          iq.setID(id); -        iq.addPayload(payload); +        if(payload != null) { +            iq.addPayload(payload); +        }          return iq;      } -     + +    public static IQ createResult(JID to, JID from, String id) { +        return createResult(to, from, id, null); +    } +      public static IQ createResult(JID to, JID from, String id, Payload payload) {          IQ iq = new IQ(Type.Result);          iq.setTo(to);          iq.setFrom(from);          iq.setID(id); -        iq.addPayload(payload); +        if(payload != null) { +            iq.addPayload(payload); +        }          return iq;      } +    public static IQ createError(JID to, String id) { +        return createError(to, id, ErrorPayload.Condition.BadRequest, ErrorPayload.Type.Cancel, null); +    } + +    public static IQ createError(JID to, String id, ErrorPayload.Condition condition) { +        return createError(to, id, condition, ErrorPayload.Type.Cancel, null); +    } +      public static IQ createError(JID to, String id, ErrorPayload.Condition condition, ErrorPayload.Type type) { -        IQ iq = new IQ(Type.Error); -        iq.setTo(to); -        iq.setID(id); -        iq.addPayload(new ErrorPayload(condition, type)); -        return iq; +        return createError(to, id, condition, type, null);      }      public static IQ createError(JID to, String id, ErrorPayload.Condition condition, ErrorPayload.Type type, Payload payload) { @@ -74,6 +94,18 @@ public class IQ extends Stanza {          return iq;      } +    public static IQ createError(JID to, JID from, String id) { +        return createError(to, from, id, ErrorPayload.Condition.BadRequest, ErrorPayload.Type.Cancel, null); +    } + +    public static IQ createError(JID to, JID from, String id, ErrorPayload.Condition condition) { +        return createError(to, from, id, condition, ErrorPayload.Type.Cancel, null); +    } + +    public static IQ createError(JID to, JID from, String id, ErrorPayload.Condition condition, ErrorPayload.Type type) { +        return createError(to, from, id, condition, type, null); +    } +      public static IQ createError(JID to, JID from, String id, ErrorPayload.Condition condition, ErrorPayload.Type type, Payload payload) {          IQ iq = new IQ(Type.Error);          iq.setTo(to); diff --git a/src/com/isode/stroke/queries/Request.java b/src/com/isode/stroke/queries/Request.java index 50645b4..52e3854 100644 --- a/src/com/isode/stroke/queries/Request.java +++ b/src/com/isode/stroke/queries/Request.java @@ -14,6 +14,7 @@ import com.isode.stroke.elements.IQ;  import com.isode.stroke.elements.IQ.Type;  import com.isode.stroke.elements.Payload;  import com.isode.stroke.jid.JID; +import java.util.logging.Logger;  /**   * Base class for IQ requests. @@ -22,36 +23,63 @@ public abstract class Request implements IQHandler {      protected final Type type_;      protected final IQRouter router_;      protected final JID receiver_; +	protected final JID sender_;          private boolean sent_;      private Payload payload_;      private String id_; +	private Logger logger_ = Logger.getLogger(this.getClass().getName()); +	/** +	 * Constructs a request of a certain type to a specific receiver. +	 */      public Request(IQ.Type type, JID receiver, IQRouter router) { -        this(type, receiver, null, router); +        this(type, null, receiver, null, router);      } +	/** +	 * Constructs a request of a certain type to a specific receiver, and attaches the given +	 * payload. +	 */      public Request(IQ.Type type, JID receiver, Payload payload, IQRouter router) { +        this(type, null, receiver, payload, router); +    } + +	/** +	 * Constructs a request of a certain type to a specific receiver from a specific sender. +	 */ +    public Request(IQ.Type type, JID sender, JID receiver, IQRouter router) { +    	this(type, sender, receiver, null, router); +    } + +	/** +	 * Constructs a request of a certain type to a specific receiver from a specific sender, and attaches the given +	 * payload. +	 */ +    public Request(IQ.Type type, JID sender, JID receiver, Payload payload, IQRouter router) {          type_ = type;          router_ = router;          receiver_ = receiver;          payload_ = payload; +        sender_ = sender;          sent_ = false;      } -    public void send() { +    public String send() {          assert payload_ != null; -	assert !sent_; -	sent_ = true; +		assert !sent_; +		sent_ = true; -	IQ iq = new IQ(type_); -	iq.setTo(receiver_); -	iq.addPayload(payload_); -	id_ = router_.getNewIQID(); -	iq.setID(id_); +		IQ iq = new IQ(type_); +		iq.setTo(receiver_); +		iq.setFrom(sender_); +		iq.addPayload(payload_); +		id_ = router_.getNewIQID(); +		iq.setID(id_); -	router_.addHandler(this); +		router_.addHandler(this); -	router_.sendIQ(iq); +		router_.sendIQ(iq); +    	return id_;      }      protected void setPayload(Payload payload) { @@ -66,42 +94,54 @@ public abstract class Request implements IQHandler {      public boolean handleIQ(IQ iq) {          boolean handled = false; -        if (sent_ && iq.getID().equals(id_)) { -	    if (isCorrectSender(iq.getFrom())) { -		 -		if (iq.getType().equals(IQ.Type.Result)) { -		    handleResponse(iq.getPayload(payload_), null); -		} else { -		    ErrorPayload errorPayload = iq.getPayload(new ErrorPayload()); -		    if (errorPayload != null) { -			handleResponse(null, errorPayload); -		    } else { -			handleResponse(null, new ErrorPayload(ErrorPayload.Condition.UndefinedCondition)); -		    } +        if (iq.getType() == IQ.Type.Result || iq.getType() == IQ.Type.Error) { +	        if (sent_ && iq.getID().equals(id_)) { +		    	if (isCorrectSender(iq.getFrom())) { +			 +					if (iq.getType().equals(IQ.Type.Result)) { +			    		handleResponse(iq.getPayload(payload_), null); +					} else { +			    		ErrorPayload errorPayload = iq.getPayload(new ErrorPayload()); +			    		if (errorPayload != null) { +						handleResponse(null, errorPayload); +			    		} else { +							handleResponse(null, new ErrorPayload(ErrorPayload.Condition.UndefinedCondition)); +			    		} +					} +					router_.removeHandler(this); +					handled = true; +		    	} +			}  		} -		router_.removeHandler(this); -		handled = true; -	    } -	}          return handled;      }      private boolean isCorrectSender(final JID jid) { -	if (isAccountJID(receiver_)) { -	    return isAccountJID(jid); -	} -	return (jid.compare(receiver_, JID.CompareType.WithResource) == 0); +		if (isAccountJID(receiver_)) { +	    	return isAccountJID(jid); +		} +		return (jid.compare(receiver_, JID.CompareType.WithResource) == 0);      }      private boolean isAccountJID(final JID jid) { -	// If the router's JID is not set, we don't check anything -	if (!router_.getJID().isValid()) { -	    return true; -	} +		// If the router's JID is not set, we don't check anything +		if (!router_.getJID().isValid()) { +		    return true; +		} -	return jid.isValid() ? -	    router_.getJID().compare(jid, JID.CompareType.WithoutResource) == 0 : true; +		return jid.isValid() ? +		    router_.getJID().compare(jid, JID.CompareType.WithoutResource) == 0 : true;      } +	public JID getReceiver() { +		return receiver_; +	} +	/** +	 * Returns the ID of this request. +	 * This will only be set after send() is called. +	 */ +	public String getID() { +		return id_; +	}  }
\ No newline at end of file  | 
 Swift