diff options
94 files changed, 4039 insertions, 252 deletions
diff --git a/src/com/isode/stroke/base/SafeByteArray.java b/src/com/isode/stroke/base/SafeByteArray.java new file mode 100644 index 0000000..9f91afb --- /dev/null +++ b/src/com/isode/stroke/base/SafeByteArray.java @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2011-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.base; + +public class SafeByteArray { + +} diff --git a/src/com/isode/stroke/client/Client.java b/src/com/isode/stroke/client/Client.java index 4f5d6c7..0ea6ad7 100644 --- a/src/com/isode/stroke/client/Client.java +++ b/src/com/isode/stroke/client/Client.java @@ -1,22 +1,31 @@ /* - * Copyright (c) 2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.client; +import com.isode.stroke.disco.CapsManager; +import com.isode.stroke.disco.ClientDiscoManager; +import com.isode.stroke.disco.EntityCapsManager; +import com.isode.stroke.disco.EntityCapsProvider; +import com.isode.stroke.elements.Presence; import com.isode.stroke.jid.JID; import com.isode.stroke.muc.MUCManager; import com.isode.stroke.muc.MUCRegistry; import com.isode.stroke.network.NetworkFactories; import com.isode.stroke.presence.DirectedPresenceSender; +import com.isode.stroke.presence.PresenceOracle; +import com.isode.stroke.presence.PresenceSender; import com.isode.stroke.presence.StanzaChannelPresenceSender; +import com.isode.stroke.presence.SubscriptionManager; import com.isode.stroke.pubsub.PubSubManager; import com.isode.stroke.pubsub.PubSubManagerImpl; import com.isode.stroke.queries.responders.SoftwareVersionResponder; +import com.isode.stroke.roster.XMPPRoster; +import com.isode.stroke.roster.XMPPRosterController; +import com.isode.stroke.roster.XMPPRosterImpl; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.vcards.VCardManager; /** * Provides the core functionality for writing XMPP client software. @@ -32,7 +41,21 @@ public class Client extends CoreClient { private final DirectedPresenceSender directedPresenceSender; //NOPMD, this is not better as a local variable private final StanzaChannelPresenceSender stanzaChannelPresenceSender; //NOPMD, this is not better as a local variable private final SoftwareVersionResponder softwareVersionResponder; - private final PubSubManager pubSubManager; + private final PubSubManager pubSubManager; + private final XMPPRosterImpl roster; + private final XMPPRosterController rosterController; + private final PresenceOracle presenceOracle; + private final Storages storages; + private final MemoryStorages memoryStorages; + private final VCardManager vcardManager; + private final CapsManager capsManager; + private final EntityCapsManager entityCapsManager; + private final NickManager nickManager; + private final NickResolver nickResolver; + private final SubscriptionManager subscriptionManager; + private final ClientDiscoManager discoManager; + + final Signal1<Presence> onPresenceChange = new Signal1<Presence>(); /** * Constructor. @@ -50,18 +73,42 @@ public class Client extends CoreClient { * @param networkFactories An implementation of network interaction, must * not be null. */ - public Client(final JID jid, final String password, final NetworkFactories networkFactories) { + public Client(final JID jid, final String password, final NetworkFactories networkFactories, Storages storages) { super(jid, password, networkFactories); + + this.storages = storages; + memoryStorages = new MemoryStorages(networkFactories.getCryptoProvider()); + + softwareVersionResponder = new SoftwareVersionResponder(getIQRouter()); + softwareVersionResponder.start(); + + roster = new XMPPRosterImpl(); + rosterController = new XMPPRosterController(getIQRouter(), roster, getStorages().getRosterStorage()); + + subscriptionManager = new SubscriptionManager(getStanzaChannel()); + + presenceOracle = new PresenceOracle(getStanzaChannel()); + presenceOracle.onPresenceChange.connect(onPresenceChange); + stanzaChannelPresenceSender = new StanzaChannelPresenceSender(getStanzaChannel()); directedPresenceSender = new DirectedPresenceSender(stanzaChannelPresenceSender); + discoManager = new ClientDiscoManager(getIQRouter(), directedPresenceSender, networkFactories.getCryptoProvider()); mucRegistry = new MUCRegistry(); mucManager = new MUCManager(getStanzaChannel(), getIQRouter(), directedPresenceSender, mucRegistry); - softwareVersionResponder = new SoftwareVersionResponder(getIQRouter()); - softwareVersionResponder.start(); - - pubSubManager = new PubSubManagerImpl(getStanzaChannel(), getIQRouter()); + vcardManager = new VCardManager(jid, getIQRouter(), getStorages().getVCardStorage()); + capsManager = new CapsManager(getStorages().getCapsStorage(), getStanzaChannel(), getIQRouter(), networkFactories.getCryptoProvider()); + entityCapsManager = new EntityCapsManager(capsManager, getStanzaChannel()); + + nickManager = new NickManagerImpl(jid.toBare(), vcardManager); + nickResolver = new NickResolver(jid.toBare(), roster, vcardManager, mucRegistry); + + pubSubManager = new PubSubManagerImpl(getStanzaChannel(), getIQRouter()); + } + + public Client(final JID jid, final String password, final NetworkFactories networkFactories) { + this(jid, password, networkFactories, null); } /** @@ -88,6 +135,10 @@ public class Client extends CoreClient { return pubSubManager; } + public XMPPRoster getRoster() { + return roster; + } + /** * Sets the software version of the client. * @@ -96,4 +147,63 @@ public class Client extends CoreClient { public void setSoftwareVersion(final String name, final String version, final String os) { softwareVersionResponder.setVersion(name, version, os); } + + + public void requestRoster() { + // FIXME: We should set this once when the session is finished, but there + // is currently no callback for this + if (getSession() != null) { + rosterController.setUseVersioning(getSession().getRosterVersioningSuported()); + } + rosterController.requestRoster(); + } + + public Presence getLastPresence(final JID jid) { + return presenceOracle.getLastPresence(jid); + } + + public Presence getHighestPriorityPresence(final JID bareJID) { + return presenceOracle.getHighestPriorityPresence(bareJID); + } + + public PresenceOracle getPresenceOracle() { + return presenceOracle; + } + + public NickManager getNickManager() { + return nickManager; + } + + public NickResolver getNickResolver() { + return nickResolver; + } + + public SubscriptionManager getSubscriptionManager() { + return subscriptionManager; + } + + public ClientDiscoManager getDiscoManager() { + return discoManager; + } + + public VCardManager getVCardManager() { + return vcardManager; + } + + private Storages getStorages() { + if (storages != null) { + return storages; + } + return memoryStorages; + } + + public PresenceSender getPresenceSender() { + return discoManager.getPresenceSender(); + } + + public EntityCapsProvider getEntityCapsProvider() { + return entityCapsManager; + } + + } diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java index a1c6d0a..84ea673 100644 --- a/src/com/isode/stroke/client/CoreClient.java +++ b/src/com/isode/stroke/client/CoreClient.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010-2014, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010-2014, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.client; @@ -286,6 +282,10 @@ public class CoreClient { connector_.stop(); } } + + public boolean isActive() { + return (session_ != null && !session_.isFinished()) || connector_ != null; + } public void setCertificate(final CertificateWithKey certificate) { certificate_ = certificate; @@ -477,6 +477,10 @@ public class CoreClient { } connector_ = null; } + + protected ClientSession getSession() { + return session_; + } private void resetSession() { session_.onFinished.disconnectAll(); diff --git a/src/com/isode/stroke/client/MemoryStorages.java b/src/com/isode/stroke/client/MemoryStorages.java new file mode 100644 index 0000000..9bd97a8 --- /dev/null +++ b/src/com/isode/stroke/client/MemoryStorages.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.client; + +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.disco.CapsMemoryStorage; +import com.isode.stroke.disco.CapsStorage; +import com.isode.stroke.roster.RosterMemoryStorage; +import com.isode.stroke.roster.RosterStorage; +import com.isode.stroke.vcards.VCardMemoryStorage; +import com.isode.stroke.vcards.VCardStorage; + +public class MemoryStorages implements Storages { + private VCardStorage vcardStorage; +// private AvatarStorage avatarStorage; + private CapsStorage capsStorage; + private RosterStorage rosterStorage; +// private HistoryStorage historyStorage; + + public MemoryStorages(CryptoProvider crypto) { + vcardStorage = new VCardMemoryStorage(crypto); + capsStorage = new CapsMemoryStorage(); +// avatarStorage = new AvatarMemoryStorage(); + rosterStorage = new RosterMemoryStorage(); +// #ifdef SWIFT_EXPERIMENTAL_HISTORY +// historyStorage = new SQLiteHistoryStorage(":memory:"); +// #else +// historyStorage = NULL; + + } + + @Override + public VCardStorage getVCardStorage() { + return vcardStorage; + } + + @Override + public RosterStorage getRosterStorage() { + return rosterStorage; + } + + @Override + public CapsStorage getCapsStorage() { + return capsStorage; + } + +} diff --git a/src/com/isode/stroke/client/NickManager.java b/src/com/isode/stroke/client/NickManager.java new file mode 100644 index 0000000..b88dd61 --- /dev/null +++ b/src/com/isode/stroke/client/NickManager.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.client; + +import com.isode.stroke.signals.Signal1; + +public abstract class NickManager { + public abstract String getOwnNick(); + public abstract void setOwnNick(final String nick); + + public final Signal1<String> onOwnNickChanged = new Signal1<String>(); +} diff --git a/src/com/isode/stroke/client/NickManagerImpl.java b/src/com/isode/stroke/client/NickManagerImpl.java new file mode 100644 index 0000000..17f9d6b --- /dev/null +++ b/src/com/isode/stroke/client/NickManagerImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.client; + +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot2; +import com.isode.stroke.vcards.VCardManager; + +public class NickManagerImpl extends NickManager { + private JID ownJID = new JID(); + private String ownNick = ""; + private SignalConnection vCardChangedSignal; + + NickManagerImpl(final JID ownJID, VCardManager vcardManager) { + this.ownJID = ownJID; + + vCardChangedSignal = vcardManager.onVCardChanged.connect(new Slot2<JID, VCard>() { + @Override + public void call(JID p1, VCard p2) { + handleVCardReceived(p1, p2); + } + }); + + updateOwnNickFromVCard(vcardManager.getVCard(ownJID.toBare())); + } + + public void delete() { + vCardChangedSignal.disconnect(); + } + + @Override + public String getOwnNick() { + return ownNick; + } + + @Override + public void setOwnNick(final String nick) { + } + + void handleVCardReceived(final JID jid, VCard vcard) { + if (jid.compare(ownJID, JID.CompareType.WithoutResource) != 0) { + return; + } + updateOwnNickFromVCard(vcard); + } + + void updateOwnNickFromVCard(VCard vcard) { + String nick = null; + if (vcard != null && !vcard.getNickname().isEmpty()) { + nick = vcard.getNickname(); + } + if (ownNick != nick && nick != null && !nick.equals(ownNick)) { + ownNick = nick; + onOwnNickChanged.emit(ownNick); + } + } + +} diff --git a/src/com/isode/stroke/client/NickResolver.java b/src/com/isode/stroke/client/NickResolver.java new file mode 100644 index 0000000..fe2b129 --- /dev/null +++ b/src/com/isode/stroke/client/NickResolver.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.client; + +import java.util.Collection; + +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.muc.MUCRegistry; +import com.isode.stroke.roster.XMPPRoster; +import com.isode.stroke.signals.Signal2; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.Slot2; +import com.isode.stroke.signals.Slot3; +import com.isode.stroke.vcards.VCardManager; + +public class NickResolver { + private JID ownJID_; + private String ownNick_; + private XMPPRoster xmppRoster_; + private MUCRegistry mucRegistry_; + private VCardManager vcardManager_; + + public final Signal2<JID, String> onNickChanged = new Signal2<JID, String>(); + + public NickResolver(final JID ownJID, XMPPRoster xmppRoster, VCardManager vcardManager, MUCRegistry mucRegistry) { + ownJID_ = ownJID; + xmppRoster_ = xmppRoster; + vcardManager_ = vcardManager; + if (vcardManager_ != null) { + vcardManager_.onVCardChanged.connect(new Slot2<JID, VCard>() { + @Override + public void call(JID p1, VCard p2) { + handleVCardReceived(p1, p2); + } + }); + } + mucRegistry_ = mucRegistry; + xmppRoster_.onJIDUpdated.connect(new Slot3<JID, String, Collection<String>>() { + @Override + public void call(JID p1, String p2, Collection<String> p3) { + handleJIDUpdated(p1, p2, p3); + } + }); + xmppRoster_.onJIDAdded.connect(new Slot1<JID>() { + @Override + public void call(JID p1) { + handleJIDAdded(p1); + } + }); + } + + void handleJIDUpdated(final JID jid, final String previousNick, final Collection<String> groups) { + onNickChanged.emit(jid, previousNick); + } + + void handleJIDAdded(final JID jid) { + String oldNick= jidToNick(jid); + onNickChanged.emit(jid, oldNick); + } + + public String jidToNick(final JID jid) { + if (jid.toBare().equals(ownJID_)) { + if (ownNick_ != null && !ownNick_.isEmpty()) { + return ownNick_; + } + } + + if (mucRegistry_ != null && mucRegistry_.isMUC(jid.toBare()) ) { + return jid.getResource().isEmpty() ? jid.toBare().toString() : jid.getResource(); + } + + if (xmppRoster_.containsJID(jid) && !xmppRoster_.getNameForJID(jid).isEmpty()) { + return xmppRoster_.getNameForJID(jid); + } + + return jid.toBare().toString(); + } + + void handleVCardReceived(final JID jid, VCard ownVCard) { + if (jid.compare(ownJID_, JID.CompareType.WithoutResource) != 0) { + return; + } + ownNick_ = ownJID_.toString(); + if (ownVCard != null) { + if (!ownVCard.getNickname().isEmpty()) { + ownNick_ = ownVCard.getNickname(); + } else if (!ownVCard.getGivenName().isEmpty()) { + ownNick_ = ownVCard.getGivenName(); + } else if (!ownVCard.getFullName().isEmpty()) { + ownNick_ = ownVCard.getFullName(); + } + } + } + +} diff --git a/src/com/isode/stroke/client/Storages.java b/src/com/isode/stroke/client/Storages.java new file mode 100644 index 0000000..188d1c6 --- /dev/null +++ b/src/com/isode/stroke/client/Storages.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.client; + +import com.isode.stroke.disco.CapsStorage; +import com.isode.stroke.roster.RosterStorage; +import com.isode.stroke.vcards.VCardStorage; + +public interface Storages { + VCardStorage getVCardStorage(); +// AvatarStorage getAvatarStorage(); +// CapsStorage getCapsStorage(); + RosterStorage getRosterStorage(); +// HistoryStorage getHistoryStorage(); + CapsStorage getCapsStorage(); +} diff --git a/src/com/isode/stroke/crypto/CryptoProvider.java b/src/com/isode/stroke/crypto/CryptoProvider.java new file mode 100644 index 0000000..1c1a29e --- /dev/null +++ b/src/com/isode/stroke/crypto/CryptoProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.crypto; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.SafeByteArray; + +public abstract class CryptoProvider { + + public abstract Hash createSHA1(); + public abstract Hash createMD5(); + public abstract ByteArray getHMACSHA1(final SafeByteArray key, final ByteArray data); + public abstract ByteArray getHMACSHA1(final ByteArray key, final ByteArray data); + public abstract boolean isMD5AllowedForCrypto(); + + // Convenience + public ByteArray getSHA1Hash(final SafeByteArray data) { + return createSHA1().update(data).getHash(); + } + + public ByteArray getSHA1Hash(final ByteArray data) { + return createSHA1().update(data).getHash(); + } + + public ByteArray getMD5Hash(final SafeByteArray data) { + return createMD5().update(data).getHash(); + } + + public ByteArray getMD5Hash(final ByteArray data) { + return createMD5().update(data).getHash(); + } + +} diff --git a/src/com/isode/stroke/crypto/Hash.java b/src/com/isode/stroke/crypto/Hash.java new file mode 100644 index 0000000..0d3f7cb --- /dev/null +++ b/src/com/isode/stroke/crypto/Hash.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.crypto; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.SafeByteArray; + +public interface Hash { + Hash update(final ByteArray data); + Hash update(final SafeByteArray data); + + ByteArray getHash(); +} diff --git a/src/com/isode/stroke/disco/CapsInfoGenerator.java b/src/com/isode/stroke/disco/CapsInfoGenerator.java new file mode 100644 index 0000000..2bbd843 --- /dev/null +++ b/src/com/isode/stroke/disco/CapsInfoGenerator.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.elements.CapsInfo; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.Form; +import com.isode.stroke.elements.FormField; +import com.isode.stroke.stringcodecs.Base64; + +public class CapsInfoGenerator { + private String node_; + private CryptoProvider crypto_; + + private final static Comparator<FormField> compareFields = new Comparator<FormField>() { + @Override + public int compare(FormField lhs, FormField rhs) { + return lhs.getName().compareTo(rhs.getName()); + } + }; + + CapsInfoGenerator(final String node, CryptoProvider crypto) { + this.node_ = node; + this.crypto_ = crypto; + } + + CapsInfo generateCapsInfo(final DiscoInfo discoInfo) { + String serializedCaps = ""; + + List<DiscoInfo.Identity> identities = discoInfo.getIdentities(); + Collections.sort(identities); + for (final DiscoInfo.Identity identity : identities) { + serializedCaps += identity.getCategory() + "/" + identity.getType() + + "/" + identity.getLanguage() + "/" + identity.getName() + + "<"; + } + + List<String> features = discoInfo.getFeatures(); + Collections.sort(features); + for (final String feature : features) { + serializedCaps += feature + "<"; + } + + for (Form extension : discoInfo.getExtensions()) { + serializedCaps += extension.getFormType() + "<"; + List<FormField> fields = extension.getFields(); + Collections.sort(fields, compareFields); + for (FormField field : fields) { + if ("FORM_TYPE".equals(field.getName())) { + continue; + } + serializedCaps += field.getName() + "<"; + List<String> values = field.getValues(); + Collections.sort(values); + for (final String value : values) { + serializedCaps += value + "<"; + } + } + } + + String version = Base64.encode(crypto_ .getSHA1Hash(new ByteArray(serializedCaps))); + return new CapsInfo(node_, version, "sha-1"); + } + +} diff --git a/src/com/isode/stroke/disco/CapsManager.java b/src/com/isode/stroke/disco/CapsManager.java new file mode 100644 index 0000000..2e4c45b --- /dev/null +++ b/src/com/isode/stroke/disco/CapsManager.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.isode.stroke.client.StanzaChannel; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.elements.CapsInfo; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.Slot2; + +public class CapsManager extends CapsProvider { + + private final IQRouter iqRouter; + private final CryptoProvider crypto; + private final CapsStorage capsStorage; + private boolean warnOnInvalidHash; + private Set<String> requestedDiscoInfos = new HashSet<String>(); + private Set<CapsPair> failingCaps = new HashSet<CapsPair>(); + private Map<String, Set<CapsPair>> fallbacks = new HashMap<String, Set<CapsPair>>(); + + private class CapsPair { + JID jid; + String node; + + CapsPair(JID j, String n) {jid = j; node = n;} + + @Override + public boolean equals(Object o) { + if (!(o instanceof CapsPair)) return false; + CapsPair o1 = (CapsPair) o; + return jid.equals(o1.jid) && node.equals(o1.node); + } + + @Override public int hashCode() {return jid.hashCode() * 5 + node.hashCode();} + } + + public CapsManager(CapsStorage capsStorage, StanzaChannel stanzaChannel, + IQRouter iqRouter, CryptoProvider crypto) { + this.iqRouter = iqRouter; + this.crypto = crypto; + this.capsStorage = capsStorage; + this.warnOnInvalidHash = true; + + stanzaChannel.onPresenceReceived.connect(new Slot1<Presence>() { + @Override + public void call(Presence p1) { + handlePresenceReceived(p1); + } + }); + stanzaChannel.onAvailableChanged.connect(new Slot1<Boolean>() { + @Override + public void call(Boolean p1) { + handleStanzaChannelAvailableChanged(p1); + } + }); + } + + private void handlePresenceReceived(Presence presence) { + CapsInfo capsInfo = presence.getPayload(new CapsInfo()); + if (capsInfo == null || !capsInfo.getHash().equals("sha-1") + || presence.getPayload(new ErrorPayload()) != null) { + return; + } + String hash = capsInfo.getVersion(); + if (capsStorage.getDiscoInfo(hash) != null) { + return; + } + if (failingCaps.contains(new CapsPair(presence.getFrom(), hash))) { + return; + } + if (requestedDiscoInfos.contains(hash)) { + Set<CapsPair> fallback = fallbacks.get(hash); + if (fallback == null) fallbacks.put(hash, fallback = new HashSet<CapsPair>()); + fallback.add(new CapsPair(presence.getFrom(), capsInfo.getNode())); + return; + } + requestDiscoInfo(presence.getFrom(), capsInfo.getNode(), hash); + } + + private void handleStanzaChannelAvailableChanged(boolean available) { + if (available) { + failingCaps.clear(); + fallbacks.clear(); + requestedDiscoInfos.clear(); + } + } + + private void handleDiscoInfoReceived(final JID from, final String hash, DiscoInfo discoInfo, ErrorPayload error) { + requestedDiscoInfos.remove(hash); + if (error != null || discoInfo == null + || !new CapsInfoGenerator("", crypto).generateCapsInfo(discoInfo).getVersion().equals(hash)) { + if (warnOnInvalidHash && error == null && discoInfo != null) { +// std.cerr << "Warning: Caps from " << from.toString() << " do not verify" << std.endl; + } + failingCaps.add(new CapsPair(from, hash)); + Set<CapsPair> i = fallbacks.get(hash); + if (i != null && !i.isEmpty()) { + CapsPair fallbackAndNode = i.iterator().next(); + i.remove(fallbackAndNode); + requestDiscoInfo(fallbackAndNode.jid, fallbackAndNode.node, hash); + } + return; + } + fallbacks.remove(hash); + capsStorage.setDiscoInfo(hash, discoInfo); + onCapsAvailable.emit(hash); + } + + private void requestDiscoInfo(final JID jid, final String node, final String hash) { + GetDiscoInfoRequest request = GetDiscoInfoRequest.create(jid, node + + "#" + hash, iqRouter); + request.onResponse.connect(new Slot2<DiscoInfo, ErrorPayload>() { + @Override + public void call(DiscoInfo p1, ErrorPayload p2) { + handleDiscoInfoReceived(jid, hash, p1, p2); + } + }); + requestedDiscoInfos.add(hash); + request.send(); + } + + @Override + DiscoInfo getCaps(String hash) { + return capsStorage.getDiscoInfo(hash); + } + + // Mainly for testing purposes + void setWarnOnInvalidHash(boolean b) { + warnOnInvalidHash = b; + } + +} diff --git a/src/com/isode/stroke/disco/CapsMemoryStorage.java b/src/com/isode/stroke/disco/CapsMemoryStorage.java new file mode 100644 index 0000000..81e0e77 --- /dev/null +++ b/src/com/isode/stroke/disco/CapsMemoryStorage.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import java.util.HashMap; +import java.util.Map; + +import com.isode.stroke.elements.DiscoInfo; + +public class CapsMemoryStorage implements CapsStorage { + private Map<String, DiscoInfo> caps = new HashMap<String, DiscoInfo>(); + + @Override + public DiscoInfo getDiscoInfo(String s) { + return caps.get(s); + } + + @Override + public void setDiscoInfo(String s, DiscoInfo d) { + caps.put(s, d); + + } + +} diff --git a/src/com/isode/stroke/disco/CapsProvider.java b/src/com/isode/stroke/disco/CapsProvider.java new file mode 100644 index 0000000..d83eebc --- /dev/null +++ b/src/com/isode/stroke/disco/CapsProvider.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.signals.Signal1; + +public abstract class CapsProvider { + abstract DiscoInfo getCaps(final String hash); + + public final Signal1<String> onCapsAvailable = new Signal1<String>(); +} diff --git a/src/com/isode/stroke/disco/CapsStorage.java b/src/com/isode/stroke/disco/CapsStorage.java new file mode 100644 index 0000000..07c1cd7 --- /dev/null +++ b/src/com/isode/stroke/disco/CapsStorage.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import com.isode.stroke.elements.DiscoInfo; + +public interface CapsStorage { + DiscoInfo getDiscoInfo(final String s); + void setDiscoInfo(final String s, DiscoInfo d); +} diff --git a/src/com/isode/stroke/disco/ClientDiscoManager.java b/src/com/isode/stroke/disco/ClientDiscoManager.java new file mode 100644 index 0000000..3770fbc --- /dev/null +++ b/src/com/isode/stroke/disco/ClientDiscoManager.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.elements.CapsInfo; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.presence.PayloadAddingPresenceSender; +import com.isode.stroke.presence.PresenceSender; +import com.isode.stroke.queries.IQRouter; + +public class ClientDiscoManager { + private PayloadAddingPresenceSender presenceSender; + private CryptoProvider crypto; + private DiscoInfoResponder discoInfoResponder; + private String capsNode; + private CapsInfo capsInfo; + + /** + * Constructs the manager + * + * \param iqRouter the router on which requests will be answered \param + * presenceSender the presence sender to which all outgoing presence (with + * caps information) will be sent. + */ + public ClientDiscoManager(IQRouter iqRouter, PresenceSender presenceSender, + CryptoProvider crypto) { + this.crypto = crypto; + discoInfoResponder = new DiscoInfoResponder(iqRouter); + discoInfoResponder.start(); + this.presenceSender = new PayloadAddingPresenceSender(presenceSender); + } + + void delete() { + discoInfoResponder.stop(); + } + + /** + * Needs to be called before calling setDiscoInfo(). + */ + public void setCapsNode(final String node) { + capsNode = node; + } + + /** + * Sets the capabilities of the client. + */ + public void setDiscoInfo(final DiscoInfo discoInfo) { + capsInfo = new CapsInfoGenerator(capsNode, crypto).generateCapsInfo(discoInfo); + discoInfoResponder.clearDiscoInfo(); + discoInfoResponder.setDiscoInfo(discoInfo); + discoInfoResponder.setDiscoInfo( + capsInfo.getNode() + "#" + capsInfo.getVersion(), discoInfo); + presenceSender.setPayload(capsInfo); + } + + /** + * Returns the presence sender through which all outgoing presence should be + * sent. The manager will add the necessary caps information, and forward it + * to the presence sender passed at construction time. + */ + public PresenceSender getPresenceSender() { + return presenceSender; + } + + /** + * Called when the client is connected. This resets the presence sender, + * such that it assumes initial presence hasn't been sent yet. + */ + void handleConnected() { + presenceSender.reset(); + } + +} diff --git a/src/com/isode/stroke/disco/DiscoInfoResponder.java b/src/com/isode/stroke/disco/DiscoInfoResponder.java new file mode 100644 index 0000000..c0edca2 --- /dev/null +++ b/src/com/isode/stroke/disco/DiscoInfoResponder.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import java.util.HashMap; +import java.util.Map; + +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.GetResponder; +import com.isode.stroke.queries.IQRouter; + +public class DiscoInfoResponder extends GetResponder<DiscoInfo> { + private DiscoInfo info_; + private Map<String, DiscoInfo> nodeInfo_ = new HashMap<String, DiscoInfo>(); + + public DiscoInfoResponder(IQRouter router) { + super(new DiscoInfo(), router); + } + + void clearDiscoInfo() { + info_ = new DiscoInfo(); + nodeInfo_.clear(); + } + + void setDiscoInfo(final DiscoInfo info) { + info_ = info; + } + + void setDiscoInfo(final String node, final DiscoInfo info) { + DiscoInfo newInfo = info; + newInfo.setNode(node); + nodeInfo_.put(node, newInfo); + } + + protected boolean handleGetRequest(final JID from, final JID j, final String id, DiscoInfo info) { + if (info.getNode().isEmpty()) { + sendResponse(from, id, info_); + } + else { + DiscoInfo i = nodeInfo_.get(info.getNode()); + if (i != null) { + sendResponse(from, id, i); + } + else { + sendError(from, id, ErrorPayload.Condition.ItemNotFound, ErrorPayload.Type.Cancel); + } + } + return true; + } +} diff --git a/src/com/isode/stroke/disco/EntityCapsManager.java b/src/com/isode/stroke/disco/EntityCapsManager.java new file mode 100644 index 0000000..a41ec11 --- /dev/null +++ b/src/com/isode/stroke/disco/EntityCapsManager.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import java.util.HashMap; +import java.util.Map; + +import com.isode.stroke.client.StanzaChannel; +import com.isode.stroke.elements.CapsInfo; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.jid.JID; +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>(); + + public EntityCapsManager(CapsProvider capsProvider, StanzaChannel stanzaChannel) { + this.capsProvider = capsProvider; + + stanzaChannel.onPresenceReceived.connect(new Slot1<Presence>() { + @Override + public void call(Presence p1) { + handlePresenceReceived(p1); + } + }); + stanzaChannel.onAvailableChanged.connect(new Slot1<Boolean>() { + @Override + public void call(Boolean p1) { + handleStanzaChannelAvailableChanged(p1); + } + }); + capsProvider.onCapsAvailable.connect(new Slot1<String>() { + @Override + public void call(String p1) { + handleCapsAvailable(p1); + } + }); + } + + 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); + } + } + } + + private void handleStanzaChannelAvailableChanged(boolean available) { + if (available) { + for (JID i : caps.keySet()) { + onCapsChanged.emit(i); + } + caps.clear(); + } + } + + private void handleCapsAvailable(final String hash) { + // TODO: Use Boost.Bimap ? + for (JID i : caps.keySet()) { + if (caps.get(i).equals(hash)) { + onCapsChanged.emit(i); + } + } + } + + public DiscoInfo getCaps(final JID jid) { + if (caps.containsKey(jid)) { + return capsProvider.getCaps(caps.get(jid)); + } + return new DiscoInfo(); + } +} diff --git a/src/com/isode/stroke/disco/EntityCapsProvider.java b/src/com/isode/stroke/disco/EntityCapsProvider.java new file mode 100644 index 0000000..fd30173 --- /dev/null +++ b/src/com/isode/stroke/disco/EntityCapsProvider.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.jid.JID; +import com.isode.stroke.signals.Signal1; + +public abstract class EntityCapsProvider { + /** + * Returns the service discovery information of the given JID. + */ + public abstract DiscoInfo getCaps(final JID jid); + + /** + * Emitted when the capabilities of a JID changes. + */ + public final Signal1<JID> onCapsChanged = new Signal1<JID>(); +} diff --git a/src/com/isode/stroke/disco/GetDiscoInfoRequest.java b/src/com/isode/stroke/disco/GetDiscoInfoRequest.java new file mode 100644 index 0000000..f820b2f --- /dev/null +++ b/src/com/isode/stroke/disco/GetDiscoInfoRequest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.disco; + +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.GenericRequest; +import com.isode.stroke.queries.IQRouter; + +public class GetDiscoInfoRequest extends GenericRequest<DiscoInfo> { + + public static GetDiscoInfoRequest create(final JID jid, IQRouter router) { + return new GetDiscoInfoRequest(jid, router); + } + + public static GetDiscoInfoRequest create(final JID jid, final String node, IQRouter router) { + return new GetDiscoInfoRequest(jid, node, router); + } + + private GetDiscoInfoRequest(final JID jid, IQRouter router) { + super(IQ.Type.Get, jid, new DiscoInfo(), router); + } + + private GetDiscoInfoRequest(final JID jid, final String node, IQRouter router) { + this(jid, router); + getPayloadGeneric().setNode(node); + } + +} diff --git a/src/com/isode/stroke/elements/ChatState.java b/src/com/isode/stroke/elements/ChatState.java index 151eec4..b602b8e 100644 --- a/src/com/isode/stroke/elements/ChatState.java +++ b/src/com/isode/stroke/elements/ChatState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Isode Limited. + * Copyright (c) 2010-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -51,4 +51,4 @@ public class ChatState extends Payload { NotNull.exceptIfNull(state, "state"); state_ = state; } -}
\ No newline at end of file +} diff --git a/src/com/isode/stroke/elements/Idle.java b/src/com/isode/stroke/elements/Idle.java new file mode 100644 index 0000000..f76cf8a --- /dev/null +++ b/src/com/isode/stroke/elements/Idle.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +package com.isode.stroke.elements; + +import java.util.Date; + +public class Idle extends Payload { + public Idle() {} + public Idle(Date since) { + since_ = since; + } + + public void setSince(Date since) { + since_ = since; + } + + public Date getSince() { + return since_; + } + + private Date since_; +} diff --git a/src/com/isode/stroke/elements/MUCInvitationPayload.java b/src/com/isode/stroke/elements/MUCInvitationPayload.java index 9d0195b..464e5ff 100644 --- a/src/com/isode/stroke/elements/MUCInvitationPayload.java +++ b/src/com/isode/stroke/elements/MUCInvitationPayload.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2011, Kevin Smith + * Copyright (c) 2011-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.elements; @@ -20,6 +16,8 @@ public class MUCInvitationPayload extends Payload { private String password_; private String reason_; private String thread_; + private boolean impromptu_; + /** * Create the payload @@ -45,6 +43,22 @@ public class MUCInvitationPayload extends Payload { } /** + * Set the impromptu value + * @param b value to set + */ + public void setIsImpromptu(boolean b) { + impromptu_ = b; + } + + /** + * Get the impromptu value + * @return impromptu value + */ + public boolean getIsImpromptu() { + return impromptu_; + } + + /** * Set the jabber ID * @param jid jabber Id, not null */ diff --git a/src/com/isode/stroke/elements/Presence.java b/src/com/isode/stroke/elements/Presence.java index 392573f..ff3b0df 100644 --- a/src/com/isode/stroke/elements/Presence.java +++ b/src/com/isode/stroke/elements/Presence.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010-2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.elements; @@ -119,6 +115,10 @@ public class Presence extends Stanza { public void setPriority(int priority) { updatePayload(new Priority(priority)); } + + public boolean isAvailable() { + return type_ != null && type_ == Type.Available; + } @Override public String toString() { diff --git a/src/com/isode/stroke/elements/PrivateStorage.java b/src/com/isode/stroke/elements/PrivateStorage.java index 171e56e..09e173f 100644 --- a/src/com/isode/stroke/elements/PrivateStorage.java +++ b/src/com/isode/stroke/elements/PrivateStorage.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.elements; @@ -15,7 +11,9 @@ package com.isode.stroke.elements; */ public class PrivateStorage extends Payload { - /** + public PrivateStorage() {} + + /** * Constructor * @param p payload, not null */ diff --git a/src/com/isode/stroke/elements/Replace.java b/src/com/isode/stroke/elements/Replace.java new file mode 100644 index 0000000..65c5216 --- /dev/null +++ b/src/com/isode/stroke/elements/Replace.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +package com.isode.stroke.elements; + +public class Replace extends Payload { + private String replaceID_; + + public Replace() { + this(""); + } + + public Replace(String id) { + replaceID_ = id; + } + + public String getID() { + return replaceID_; + } + + public void setID(String id) { + replaceID_ = id; + } + +} diff --git a/src/com/isode/stroke/elements/RosterItemPayload.java b/src/com/isode/stroke/elements/RosterItemPayload.java index a80ecc6..c080915 100644 --- a/src/com/isode/stroke/elements/RosterItemPayload.java +++ b/src/com/isode/stroke/elements/RosterItemPayload.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Isode Limited, London, England. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ /* @@ -23,15 +23,23 @@ public class RosterItemPayload { }; public RosterItemPayload() { + jid_ = new JID(); + name_ = ""; subscription_ = Subscription.None; ask_ = false; + groups_ = new ArrayList<String>(); } - public RosterItemPayload(JID jid, String name, Subscription subscription) { + public RosterItemPayload(JID jid, String name, Subscription subscription, Collection<String> groups) { jid_ = jid; name_ = name; subscription_ = subscription; ask_ = false; + groups_ = groups; + } + + public RosterItemPayload(JID jid, String name, Subscription subscription) { + this(jid, name, subscription, new ArrayList<String>()); } public void setJID(JID jid) { @@ -81,6 +89,6 @@ public class RosterItemPayload { private JID jid_; private String name_; private Subscription subscription_; - private ArrayList<String> groups_ = new ArrayList<String>(); + private Collection<String> groups_; private boolean ask_; } diff --git a/src/com/isode/stroke/elements/RosterPayload.java b/src/com/isode/stroke/elements/RosterPayload.java index 6208216..5ca32f8 100644 --- a/src/com/isode/stroke/elements/RosterPayload.java +++ b/src/com/isode/stroke/elements/RosterPayload.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Isode Limited, London, England. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ /* @@ -9,13 +9,16 @@ package com.isode.stroke.elements; import com.isode.stroke.jid.JID; + import java.util.ArrayList; -import java.util.Collection; +import java.util.List; /** * Roster. */ public class RosterPayload extends Payload { + private final ArrayList<RosterItemPayload> items_ = new ArrayList<RosterItemPayload>(); + private String version_; public RosterPayload() { } @@ -33,9 +36,16 @@ public class RosterPayload extends Payload { items_.add(item); } - public Collection<RosterItemPayload> getItems() { + public List<RosterItemPayload> getItems() { return items_; } - private final ArrayList<RosterItemPayload> items_ = new ArrayList<RosterItemPayload>(); + public String getVersion() { + return version_; + } + + public void setVersion(String version) { + this.version_ = version; + } + } diff --git a/src/com/isode/stroke/elements/SecurityLabel.java b/src/com/isode/stroke/elements/SecurityLabel.java new file mode 100644 index 0000000..8134811 --- /dev/null +++ b/src/com/isode/stroke/elements/SecurityLabel.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.elements; + +import java.util.ArrayList; +import java.util.Collection; + +public class SecurityLabel extends Payload { + + private Collection<String> equivalentLabels = new ArrayList<String>(); + private String foregroundColor; + private String displayMarking; + private String backgroundColor; + private String label; + + final Collection<String> getEquivalentLabels() { + return equivalentLabels; + } + + void setEquivalentLabels(final Collection<String> value) { + this.equivalentLabels = value ; + } + + void addEquivalentLabel(final String value) { + this.equivalentLabels.add(value); + } + + final String getForegroundColor() { + return foregroundColor; + } + + void setForegroundColor(final String value) { + this.foregroundColor = value ; + } + + final String getDisplayMarking() { + return displayMarking; + } + + void setDisplayMarking(final String value) { + this.displayMarking = value ; + } + + final String getBackgroundColor() { + return backgroundColor; + } + + void setBackgroundColor(final String value) { + this.backgroundColor = value ; + } + + final String getLabel() { + return label; + } + + void setLabel(final String value) { + this.label = value ; + } + +} diff --git a/src/com/isode/stroke/elements/SecurityLabelsCatalog.java b/src/com/isode/stroke/elements/SecurityLabelsCatalog.java new file mode 100644 index 0000000..4bb9493 --- /dev/null +++ b/src/com/isode/stroke/elements/SecurityLabelsCatalog.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.elements; + +import java.util.Collection; + +import com.isode.stroke.jid.JID; + +public class SecurityLabelsCatalog extends Payload { + private JID to_; + private String name_; + private String description_; + private Collection<Item> items_; + + public class Item { + private SecurityLabel label_; + private String selector_; + private boolean default_; + + public SecurityLabel getLabel() { + return label_; + } + + void setLabel(SecurityLabel label) { + label_ = label; + } + + final String getSelector() { return selector_; } + + void setSelector(final String selector) { + selector_ = selector; + } + + boolean getIsDefault() { return default_; } + + void setIsDefault(boolean isDefault) { + default_ = isDefault; + } + }; + + public SecurityLabelsCatalog() { + this(new JID()); + } + + public SecurityLabelsCatalog(final JID to) { + to_ = to; + } + + public final Collection<Item> getItems() { + return items_; + } + + public void addItem(final Item item) { + items_.add(item); + } + + public final JID getTo() { + return to_; + } + + public void setTo(final JID to) { + to_ = to; + } + + public final String getName() { + return name_; + } + + public void setName(final String name) { + name_ = name; + } + + public final String getDescription() { + return description_; + } + + public void setDescription(final String description) { + description_ = description; + } + +} diff --git a/src/com/isode/stroke/elements/Stanza.java b/src/com/isode/stroke/elements/Stanza.java index c542e8b..9a4b907 100644 --- a/src/com/isode/stroke/elements/Stanza.java +++ b/src/com/isode/stroke/elements/Stanza.java @@ -1,15 +1,13 @@ /* - * Copyright (c) 2010-2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.elements; import com.isode.stroke.jid.JID; + +import java.util.Date; import java.util.Vector; /** @@ -48,7 +46,8 @@ public abstract class Stanza implements Element { * @param type payload type object instance, not null * @return payload of given type, can be null */ - public <T extends Payload> T getPayload(T type) { + @SuppressWarnings("unchecked") + public <T extends Payload> T getPayload(T type) { for (Payload payload : payloads_) { if (payload.getClass().isAssignableFrom(type.getClass())) { return (T)payload; @@ -63,7 +62,8 @@ public abstract class Stanza implements Element { * @param type payload type object instance, not null * @return list of payloads of given type, not null but can be empty */ - public <T extends Payload> Vector<T> getPayloads(T type) { + @SuppressWarnings("unchecked") + public <T extends Payload> Vector<T> getPayloads(T type) { Vector<T> results = new Vector<T>(); for (Payload payload : payloads_) { if (payload.getClass().isAssignableFrom(type.getClass())) { @@ -168,4 +168,8 @@ public abstract class Stanza implements Element { " id=\"" + id_ + "\""; } + public Date getTimestamp() { + Delay delay = getPayload(new Delay()); + return delay != null ? delay.getStamp() : null; + } } diff --git a/src/com/isode/stroke/elements/StatusShow.java b/src/com/isode/stroke/elements/StatusShow.java index 14cfe2c..c6b2b14 100644 --- a/src/com/isode/stroke/elements/StatusShow.java +++ b/src/com/isode/stroke/elements/StatusShow.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.elements; @@ -25,11 +21,11 @@ public class StatusShow extends Payload { type_ = type; } - void setType(Type type) { + public void setType(Type type) { type_ = type; } - Type getType() { + public Type getType() { return type_; } @@ -50,4 +46,27 @@ public class StatusShow extends Payload { } return "Unknown"; } + + /** + * Can be used for rough ordering of Types. + * Greater magnitude = more available. + */ + public static int typeToAvailabilityOrdering(Type type) { + switch (type) { + case Online: return 4; + case FFC: return 5; + case Away: return 2; + case XA: return 1; + case DND: return 3; + case None: return 0; + } + assert(false); + return 0; + } + + @Override + public String toString() { + return "StatusShow : " + type_.toString(); + } + } diff --git a/src/com/isode/stroke/elements/VCard.java b/src/com/isode/stroke/elements/VCard.java new file mode 100644 index 0000000..f9b294b --- /dev/null +++ b/src/com/isode/stroke/elements/VCard.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.elements; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.jid.JID; + +public class VCard extends Payload implements Serializable { + private String version_ = ""; + private String fullName_ = ""; + private String familyName_ = ""; + private String givenName_ = ""; + private String middleName_ = ""; + private String prefix_ = ""; + private String suffix_ = ""; +// private //String email_; + private ByteArray photo_; + private String photoType_ = ""; + private String nick_ = ""; + private Date birthday_; + private String unknownContent_ = ""; + private List<EMailAddress> emailAddresses_; + private List<Telephone> telephones_; + private List<Address> addresses_; + private List<AddressLabel> addressLabels_; + private List<JID> jids_; + private String description_ = ""; + private List<Organization> organizations_; + private List<String> titles_; + private List<String> roles_; + private List<String> urls_; + + public static class EMailAddress { + public boolean isHome; + public boolean isWork; + public boolean isInternet; + public boolean isPreferred; + public boolean isX400; + public String address; + }; + + public static class Telephone { + public boolean isHome; + public boolean isWork; + public boolean isVoice; + public boolean isFax; + public boolean isPager; + public boolean isMSG; + public boolean isCell; + public boolean isVideo; + public boolean isBBS; + public boolean isModem; + public boolean isISDN; + public boolean isPCS; + public boolean isPreferred; + public String number; + }; + + public static enum DeliveryType { + DomesticDelivery, + InternationalDelivery, + None + }; + + public static class Address { + public boolean isHome; + public boolean isWork; + public boolean isPostal; + public boolean isParcel; + public DeliveryType deliveryType; + public boolean isPreferred; + + public String poBox; + public String addressExtension; + public String street; + public String locality; + public String region; + public String postalCode; + public String country; + }; + + public static class AddressLabel { + public boolean isHome; + public boolean isWork; + public boolean isPostal; + public boolean isParcel; + public DeliveryType deliveryType; + public boolean isPreferred; + public List<String> lines = new ArrayList<String>(); + }; + + public static class Organization { + public String name; + public List<String> units = new ArrayList<String>(); + }; + + public VCard() {} + + public void setVersion(final String version) { version_ = version; } + public final String getVersion() { return version_; } + + public void setFullName(final String fullName) { fullName_ = fullName; } + public final String getFullName() { return fullName_; } + + public void setFamilyName(final String familyName) { familyName_ = familyName; } + public final String getFamilyName() { return familyName_; } + + public void setGivenName(final String givenName) { givenName_ = givenName; } + public final String getGivenName() { return givenName_; } + + public void setMiddleName(final String middleName) { middleName_ = middleName; } + public final String getMiddleName() { return middleName_; } + + public void setPrefix(final String prefix) { prefix_ = prefix; } + public final String getPrefix() { return prefix_; } + + public void setSuffix(final String suffix) { suffix_ = suffix; } + public final String getSuffix() { return suffix_; } + + + //void setEMailAddress(final String email) { email_ = email; } + //final String getEMailAddress() { return email_; } + + public void setNickname(final String nick) { nick_ = nick; } + public final String getNickname() { return nick_; } + + public void setPhoto(final ByteArray photo) { photo_ = photo; } + public final ByteArray getPhoto() { return photo_; } + + public void setPhotoType(final String photoType) { photoType_ = photoType; } + public final String getPhotoType() { return photoType_; } + + public final String getUnknownContent() { return unknownContent_; } + public void addUnknownContent(final String c) { + unknownContent_ += c; + } + + public final List<EMailAddress> getEMailAddresses() { + return emailAddresses_; + } + + public void addEMailAddress(final EMailAddress email) { + if (emailAddresses_ == null) emailAddresses_ = new ArrayList<EMailAddress>(); + emailAddresses_.add(email); + } + + public void clearEMailAddresses() { + if (emailAddresses_ != null) emailAddresses_.clear(); + } + + public void setBirthday(final Date birthday) { + birthday_ = birthday; + } + + public final Date getBirthday() { + return birthday_; + } + + public void addTelephone(final Telephone phone) { + if (telephones_ == null) telephones_ = new ArrayList<Telephone>(); + telephones_.add(phone); + } + + public void clearTelephones() { + if (telephones_ != null) telephones_.clear(); + } + + public final List<Address> getAddresses() { + return addresses_; + } + + public void addAddress(final Address address) { + if (addresses_ == null) addresses_ = new ArrayList<Address>(); + addresses_.add(address); + } + + public void clearAddresses() { + if (addresses_ != null) addresses_.clear(); + } + + public final List<AddressLabel> getAddressLabels() { + return addressLabels_; + } + + public void addAddressLabel(final AddressLabel addressLabel) { + if (addressLabels_ == null) addressLabels_ = new ArrayList<AddressLabel>(); + addressLabels_.add(addressLabel); + } + + public void clearAddressLabels() { + if (addressLabels_ != null) addressLabels_.clear(); + } + + public final List<JID> getJIDs() { + if (jids_ == null) jids_ = new ArrayList<JID>(); + return jids_; + } + + public void clearJIDs() { + if (jids_ != null) jids_.clear(); + } + + public final String getDescription() { + return description_; + } + + public void setDescription(final String description) { + this.description_ = description; + } + + public final List<Organization> getOrganizations() { + return organizations_; + } + + public void addOrganization(final Organization organization) { + if (organizations_ == null) organizations_ = new ArrayList<Organization>(); + organizations_.add(organization); + } + + public void clearOrganizations() { + if (organizations_ != null) organizations_.clear(); + } + + public final List<String> getTitles() { + return titles_; + } + + public void addTitle(final String title) { + if (titles_ == null) titles_ = new ArrayList<String>(); + titles_.add(title); + } + + public void clearTitles() { + if (titles_ != null) titles_.clear(); + } + + public final List<String> getRoles() { + return roles_; + } + + public void addRole(final String role) { + if (roles_ == null) roles_ = new ArrayList<String>(); + roles_.add(role); + } + + public void clearRoles() { + if (roles_ != null) roles_.clear(); + } + + public final List<String> getURLs() { + return urls_; + } + + public void addURL(final String url) { + if (urls_ == null) urls_ = new ArrayList<String>(); + urls_.add(url); + } + + public void clearURLs() { + if (urls_ != null) urls_.clear(); + } + + public boolean isEmpty() { + boolean empty = version_.isEmpty() && fullName_.isEmpty() && familyName_.isEmpty() && givenName_.isEmpty() && middleName_.isEmpty() && prefix_.isEmpty() && suffix_.isEmpty(); + empty &= photo_ == null || photo_.isEmpty(); + empty &= photoType_.isEmpty(); + empty &= nick_.isEmpty(); + empty &= birthday_ == null; + empty &= unknownContent_.isEmpty(); + empty &= emailAddresses_ == null || emailAddresses_.isEmpty(); + empty &= telephones_ == null || telephones_.isEmpty(); + empty &= addresses_ == null || addresses_.isEmpty(); + empty &= addressLabels_ == null || addressLabels_.isEmpty(); + empty &= jids_ == null || jids_.isEmpty(); + empty &= description_.isEmpty(); + empty &= organizations_ == null || organizations_.isEmpty(); + empty &= titles_ == null || titles_.isEmpty(); + empty &= roles_ == null || roles_.isEmpty(); + empty &= urls_ == null || urls_.isEmpty(); + return empty; + } + + public EMailAddress getPreferredEMailAddress() { + for (final EMailAddress address : emailAddresses_) { + if (address.isPreferred) { + return address; + } + } + if (emailAddresses_ != null && !emailAddresses_.isEmpty()) { + return emailAddresses_.get(0); + } + return new EMailAddress(); + } + + public void addJID(JID jid) { + jids_.add(jid); + + } + + public List<Telephone> getTelephones() { + return telephones_; + } + +} diff --git a/src/com/isode/stroke/muc/MUC.java b/src/com/isode/stroke/muc/MUC.java index 8df1b89..900bb7d 100644 --- a/src/com/isode/stroke/muc/MUC.java +++ b/src/com/isode/stroke/muc/MUC.java @@ -1,13 +1,10 @@ /* - * Copyright (c) 2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Kevin Smith + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.muc; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -32,6 +29,7 @@ import com.isode.stroke.jid.JID.CompareType; import com.isode.stroke.presence.DirectedPresenceSender; import com.isode.stroke.queries.GenericRequest; import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Signal; import com.isode.stroke.signals.Signal1; import com.isode.stroke.signals.Signal2; import com.isode.stroke.signals.Signal3; @@ -70,6 +68,8 @@ public class MUC { public Signal3<ErrorPayload,JID,MUCOccupant.Role> onRoleChangeFailed = new Signal3<ErrorPayload,JID,MUCOccupant.Role>(); + public final Signal onUnlocked = new Signal(); + private boolean createAsReservedIfNew; private IQRouter iqRouter_; private boolean joinComplete_; @@ -253,25 +253,44 @@ public class MUC { return occupants.containsKey(nick); } + public Map<String, MUCOccupant> getOccupants() { + return Collections.unmodifiableMap(occupants); + } + /** * Invite the person with give JID to the chat room * @param person jabber ID o the person to invite,not nul */ public void invitePerson(JID person) { - invitePerson(person,""); + invitePerson(person, "", false, false); + } + + /** + * Send an invite for the person to join the MUC + * @param person jabber ID of the person to invite, not null + * @param reason join reason, not null + * @param isImpromptu + */ + public void invitePerson(JID person, String reason, boolean isImpromptu) { + invitePerson(person, reason, isImpromptu, false); } + /** * Send an invite for the person to join the MUC * @param person jabber ID of the person to invite, not null * @param reason join reason, not null + * @param isImpromptu + * @param isReuseChat */ - public void invitePerson(JID person, String reason) { + public void invitePerson(JID person, String reason, boolean isImpromptu, boolean isReuseChat) { Message message = new Message(); message.setTo(person); message.setType(Message.Type.Normal); MUCInvitationPayload invite = new MUCInvitationPayload(); invite.setReason(reason); + invite.setIsImpromptu(isImpromptu); + invite.setIsContinuation(isReuseChat); invite.setJID(ownMUCJID.toBare()); message.addPayload(invite); stanzaChannel.sendMessage(message); diff --git a/src/com/isode/stroke/muc/MUCBookmarkManager.java b/src/com/isode/stroke/muc/MUCBookmarkManager.java index 78bddea..36e6d94 100644 --- a/src/com/isode/stroke/muc/MUCBookmarkManager.java +++ b/src/com/isode/stroke/muc/MUCBookmarkManager.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.muc; @@ -116,7 +112,7 @@ public class MUCBookmarkManager { storage = payload; Vector<MUCBookmark> receivedBookmarks = new Vector<MUCBookmark>(); - for (Storage.Room room : payload.getRooms()) { + if (payload != null) for (Storage.Room room : payload.getRooms()) { receivedBookmarks.add(new MUCBookmark(room)); } diff --git a/src/com/isode/stroke/network/JavaConnection.java b/src/com/isode/stroke/network/JavaConnection.java index f1d72bb..9b171d9 100644 --- a/src/com/isode/stroke/network/JavaConnection.java +++ b/src/com/isode/stroke/network/JavaConnection.java @@ -160,6 +160,7 @@ public class JavaConnection extends Connection implements EventOwner { if(selector_ != null) { try { selector_.close(); + selector_ = null; } catch (IOException e) { } } @@ -382,7 +383,7 @@ public class JavaConnection extends Connection implements EventOwner { private boolean disconnecting_ = false; private boolean disconnected_ = false; private SocketChannel socketChannel_; - private Selector selector_; + private volatile Selector selector_; private SelectionKey selectionKey_; private Worker worker_; diff --git a/src/com/isode/stroke/network/JavaCryptoProvider.java b/src/com/isode/stroke/network/JavaCryptoProvider.java new file mode 100644 index 0000000..b7dff5d --- /dev/null +++ b/src/com/isode/stroke/network/JavaCryptoProvider.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2011-2015 Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.network; + +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.crypto.Hash; +import com.isode.stroke.base.SafeByteArray; + +public class JavaCryptoProvider extends CryptoProvider { + + private static class HashProvider implements Hash { + private final MessageDigest digest; + + HashProvider(String algorithm) throws NoSuchAlgorithmException { + digest = MessageDigest.getInstance("SHA-1"); + } + + @Override + public Hash update(ByteArray data) { + digest.update(data.getData()); + return this; + } + + @Override + public Hash update(SafeByteArray data) { +// digest.update(data.getData()); + return this; + } + + @Override + public ByteArray getHash() { + return new ByteArray(digest.digest()); + } + + } + + @Override + public Hash createSHA1() { + try { + return new HashProvider("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + + @Override + public Hash createMD5() { + try { + return new HashProvider("MD5"); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + +// @Override + public ByteArray getHMACSHA1(SafeByteArray key, ByteArray data) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ByteArray getHMACSHA1(ByteArray key, ByteArray data) { + Mac mac; + try { + mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(key.getData(), mac.getAlgorithm())); + return new ByteArray(mac.doFinal(data.getData())); + } catch (NoSuchAlgorithmException e) { + return null; + } catch (InvalidKeyException e) { + return null; + } + } + + @Override + public boolean isMD5AllowedForCrypto() { + return false; + } + +} diff --git a/src/com/isode/stroke/network/JavaNetworkFactories.java b/src/com/isode/stroke/network/JavaNetworkFactories.java index aaffea3..2276a2a 100644 --- a/src/com/isode/stroke/network/JavaNetworkFactories.java +++ b/src/com/isode/stroke/network/JavaNetworkFactories.java @@ -1,9 +1,10 @@ /* - * Copyright (c) 2011-2013 Isode Limited, London, England. + * Copyright (c) 2011-2015 Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.network; +import com.isode.stroke.crypto.CryptoProvider; import com.isode.stroke.eventloop.EventLoop; import com.isode.stroke.tls.PlatformTLSFactories; import com.isode.stroke.tls.TLSContextFactory; @@ -16,6 +17,7 @@ public class JavaNetworkFactories implements NetworkFactories { connections_ = new JavaConnectionFactory(eventLoop_); dns_ = new PlatformDomainNameResolver(eventLoop_); platformTLSFactories_ = new PlatformTLSFactories(); + cryptoProvider_ = new JavaCryptoProvider(); } public TimerFactory getTimerFactory() { @@ -34,9 +36,15 @@ public class JavaNetworkFactories implements NetworkFactories { return platformTLSFactories_.getTLSContextFactory(); } + @Override + public CryptoProvider getCryptoProvider() { + return cryptoProvider_; + } + private final EventLoop eventLoop_; private final JavaTimerFactory timers_; private final JavaConnectionFactory connections_; private final PlatformDomainNameResolver dns_; private final PlatformTLSFactories platformTLSFactories_; + private final CryptoProvider cryptoProvider_; } diff --git a/src/com/isode/stroke/network/NetworkFactories.java b/src/com/isode/stroke/network/NetworkFactories.java index 678a3b7..fe20214 100644 --- a/src/com/isode/stroke/network/NetworkFactories.java +++ b/src/com/isode/stroke/network/NetworkFactories.java @@ -1,13 +1,10 @@ /* - * Copyright (c) 2011-2013 Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010 Remko Tronçon. + * Copyright (c) 2010-2015 Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.network; +import com.isode.stroke.crypto.CryptoProvider; import com.isode.stroke.tls.TLSContextFactory; public interface NetworkFactories { @@ -16,5 +13,6 @@ public interface NetworkFactories { ConnectionFactory getConnectionFactory(); DomainNameResolver getDomainNameResolver(); TLSContextFactory getTLSContextFactory(); + CryptoProvider getCryptoProvider(); } diff --git a/src/com/isode/stroke/parser/XMPPParser.java b/src/com/isode/stroke/parser/XMPPParser.java index c2c25e9..85d0e1f 100644 --- a/src/com/isode/stroke/parser/XMPPParser.java +++ b/src/com/isode/stroke/parser/XMPPParser.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.parser; @@ -35,7 +31,7 @@ public class XMPPParser implements XMLParserClient { xmlParseResult = xmlParser_.parse(data); } catch (Exception e) { parseErrorOccurred_ = true; - logger_.warning("Data " + data + " caused:\n" + e.getMessage()); + logger_.log(java.util.logging.Level.WARNING, "Data " + data + " caused:\n" + e.getMessage(), e); } if (parseErrorOccurred_ || !xmlParseResult) { logger_.warning(String.format("When parsing, %b and %b", diff --git a/src/com/isode/stroke/parser/payloadparsers/ForwardedParser.java b/src/com/isode/stroke/parser/payloadparsers/ForwardedParser.java index b7a023e..2819566 100644 --- a/src/com/isode/stroke/parser/payloadparsers/ForwardedParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/ForwardedParser.java @@ -1,10 +1,5 @@ /* - * Copyright (c) 2014 Kevin Smith and Remko Tronçon - * All rights reserved. - */ - -/* - * Copyright (c) 2014, Isode Limited, London, England. + * Copyright (c) 2014-2015, Isode Limited, London, England. * All rights reserved. */ @@ -28,13 +23,13 @@ public class ForwardedParser extends GenericPayloadParser<Forwarded> { public void handleStartElement(String element, String ns, AttributeMap attributes) { if (level_ == 1) { - if (element == "iq") { /* begin parsing a nested stanza? */ + if ("iq".equals(element)) { /* begin parsing a nested stanza? */ childParser_ = new IQParser(factories_); - } else if (element == "message") { + } else if ("message".equals(element)) { childParser_ = new MessageParser(factories_); - } else if (element == "presence") { + } else if ("presence".equals(element)) { childParser_ = new PresenceParser(factories_); - } else if (element == "delay" && ns == "urn:xmpp:delay") { + } else if ("delay".equals(element) && "urn:xmpp:delay".equals(ns)) { delayParser_ = new DelayParser(); } } diff --git a/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java index 5236d42..e443a00 100644 --- a/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java +++ b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010-2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.parser.payloadparsers; @@ -12,27 +8,27 @@ import com.isode.stroke.parser.GenericPayloadParserFactory; import com.isode.stroke.parser.GenericPayloadParserFactory2; import com.isode.stroke.parser.PayloadParserFactory; import com.isode.stroke.parser.PayloadParserFactoryCollection; -import com.isode.stroke.parser.PubSubOwnerPubSubParser; +import com.isode.stroke.parser.payloadparsers.PubSubOwnerPubSubParser; public class FullPayloadParserFactoryCollection extends PayloadParserFactoryCollection { public FullPayloadParserFactoryCollection() { /* TODO: Port more */ //addFactory(new GenericPayloadParserFactory<IBBParser>("", "http://jabber.org/protocol/ibb")); - //addFactory(new GenericPayloadParserFactory<StatusShowParser>("show", StatusShowParser.class)); - //addFactory(new GenericPayloadParserFactory<StatusParser>("status", StatusParser.class)); - //addFactory(new GenericPayloadParserFactory<ReplaceParser>("replace", "http://swift.im/protocol/replace")); + addFactory(new GenericPayloadParserFactory<StatusShowParser>("show", StatusShowParser.class)); + addFactory(new GenericPayloadParserFactory<StatusParser>("status", StatusParser.class)); + addFactory(new GenericPayloadParserFactory<ReplaceParser>("replace", "http://swift.im/protocol/replace", ReplaceParser.class)); + addFactory(new GenericPayloadParserFactory<ReplaceParser>("replace", "urn:xmpp:message-correct:0", ReplaceParser.class)); addFactory(new GenericPayloadParserFactory<LastParser>("query", "jabber:iq:last", LastParser.class)); addFactory(new GenericPayloadParserFactory<BodyParser>("body", BodyParser.class)); - //addFactory(new GenericPayloadParserFactory<SubjectParser>("subject", SubjectParser.class)); + addFactory(new GenericPayloadParserFactory<SubjectParser>("subject", SubjectParser.class)); //addFactory(new GenericPayloadParserFactory<PriorityParser>("priority", PriorityParser.class)); addFactory(new ErrorParserFactory(this)); addFactory(new SoftwareVersionParserFactory()); - //addFactory(new StorageParserFactory()); + addFactory(new GenericPayloadParserFactory<StorageParser>("storage", "storage:bookmarks", StorageParser.class)); addFactory(new RosterParserFactory()); addFactory(new GenericPayloadParserFactory<DiscoInfoParser>("query", "http://jabber.org/protocol/disco#info", DiscoInfoParser.class)); addFactory(new GenericPayloadParserFactory<DiscoItemsParser>("query", "http://jabber.org/protocol/disco#items", DiscoItemsParser.class)); addFactory(new GenericPayloadParserFactory<CapsInfoParser> ("c", "http://jabber.org/protocol/caps", CapsInfoParser.class)); - //addFactory(new CapsInfoParserFactory()); addFactory(new ResourceBindParserFactory()); addFactory(new StartSessionParserFactory()); //addFactory(new SecurityLabelParserFactory()); @@ -45,9 +41,9 @@ public class FullPayloadParserFactoryCollection extends PayloadParserFactoryColl //addFactory(new StreamInitiationParserFactory()); //addFactory(new BytestreamsParserFactory()); //addFactory(new VCardUpdateParserFactory()); - //addFactory(new VCardParserFactory()); - //addFactory(new PrivateStorageParserFactory(this)); - addFactory(new ChatStateParserFactory()); + addFactory(new GenericPayloadParserFactory<VCardParser>("vCard", "vcard-temp", VCardParser.class)); + addFactory(new PrivateStorageParserFactory(this)); + addFactory(new ChatStateParserFactory()); //addFactory(new DelayParserFactory()); addFactory(new MUCUserPayloadParserFactory(this)); addFactory(new MUCOwnerPayloadParserFactory(this)); @@ -59,6 +55,7 @@ public class FullPayloadParserFactoryCollection extends PayloadParserFactoryColl "http://jabber.org/protocol/muc#user",MUCDestroyPayloadParser.class)); addFactory(new GenericPayloadParserFactory<MUCDestroyPayloadParser>("destroy", "http://jabber.org/protocol/muc#owner",MUCDestroyPayloadParser.class)); + addFactory(new GenericPayloadParserFactory<IdleParser>("idle", "urn:xmpp:idle:1",IdleParser.class)); addFactory(new GenericPayloadParserFactory2<PubSubParser>("pubsub", "http://jabber.org/protocol/pubsub", this, PubSubParser.class)); addFactory(new GenericPayloadParserFactory2<PubSubOwnerPubSubParser>("pubsub", "http://jabber.org/protocol/pubsub#owner", this, PubSubOwnerPubSubParser.class)); diff --git a/src/com/isode/stroke/parser/payloadparsers/IdleParser.java b/src/com/isode/stroke/parser/payloadparsers/IdleParser.java new file mode 100644 index 0000000..3986168 --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/IdleParser.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2011-2015, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.parser.payloadparsers; + +import java.util.Date; + +import com.isode.stroke.base.DateTime; +import com.isode.stroke.elements.Idle; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; + +public class IdleParser extends GenericPayloadParser<Idle> { + + private int level_ = 0; + + public IdleParser() { + super(new Idle()); + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + if (level_ == 0) { + Date since = DateTime.stringToDate(attributes.getAttribute("since")); + getPayloadInternal().setSince(since); + } + ++level_; + } + + public void handleEndElement(String element, String ns) { + --level_; + } + + public void handleCharacterData(String data) { + + } + +} diff --git a/src/com/isode/stroke/parser/payloadparsers/MAMQueryParser.java b/src/com/isode/stroke/parser/payloadparsers/MAMQueryParser.java index 2543a68..dc41e9d 100644 --- a/src/com/isode/stroke/parser/payloadparsers/MAMQueryParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/MAMQueryParser.java @@ -1,10 +1,5 @@ /* -* Copyright (c) 2014 Kevin Smith and Remko Tronçon -* All rights reserved. -*/ - -/* -* Copyright (c) 2014, Isode Limited, London, England. +* Copyright (c) 2014-2015, Isode Limited, London, England. * All rights reserved. */ @@ -34,9 +29,9 @@ public class MAMQueryParser extends GenericPayloadParser<MAMQuery> { payloadInternal.setNode(nodeValue); } } else if (level_ == 1) { - if (element == "x" && ns == "jabber:x:data") { + if ("x".equals(element) && "jabber:x:data".equals(ns)) { formParser_ = new FormParser(); - } else if (element == "set" && ns == "http://jabber.org/protocol/rsm") { + } else if ("set".equals(element) && "http://jabber.org/protocol/rsm".equals(ns)) { resultSetParser_ = new ResultSetParser(); } } diff --git a/src/com/isode/stroke/parser/payloadparsers/MAMResultParser.java b/src/com/isode/stroke/parser/payloadparsers/MAMResultParser.java index cb3d7fd..3e71712 100644 --- a/src/com/isode/stroke/parser/payloadparsers/MAMResultParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/MAMResultParser.java @@ -1,10 +1,5 @@ /* -* Copyright (c) 2014 Kevin Smith and Remko Tronçon -* All rights reserved. -*/ - -/* -* Copyright (c) 2014, Isode Limited, London, England. +* Copyright (c) 2014-2015, Isode Limited, London, England. * All rights reserved. */ @@ -33,7 +28,7 @@ public class MAMResultParser extends GenericPayloadParser<MAMResult> { getPayloadInternal().setQueryID(attributeValue); } } else if (level_ == 1) { - if (element == "forwarded" && ns == "urn:xmpp:forward:0") { + if ("forwarded".equals(element) && "urn:xmpp:forward:0".equals(ns)) { payloadParser_ = new ForwardedParser(factories_); } } diff --git a/src/com/isode/stroke/parser/payloadparsers/MUCInvitationPayloadParser.java b/src/com/isode/stroke/parser/payloadparsers/MUCInvitationPayloadParser.java index 27ab0d8..473bcdd 100644 --- a/src/com/isode/stroke/parser/payloadparsers/MUCInvitationPayloadParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/MUCInvitationPayloadParser.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2011, Kevin Smith + * Copyright (c) 2011-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.parser.payloadparsers; @@ -11,6 +7,7 @@ package com.isode.stroke.parser.payloadparsers; import com.isode.stroke.elements.MUCInvitationPayload; import com.isode.stroke.jid.JID; import com.isode.stroke.parser.GenericPayloadTreeParser; +import com.isode.stroke.parser.tree.NullParserElement; import com.isode.stroke.parser.tree.ParserElement; /** @@ -31,5 +28,7 @@ public class MUCInvitationPayloadParser extends GenericPayloadTreeParser<MUCInvi invite.setPassword(root.getAttributes().getAttribute("password")); invite.setReason(root.getAttributes().getAttribute("reason")); invite.setThread(root.getAttributes().getAttribute("thread")); + ParserElement impromptuNode = root.getChild("impromptu", "http://swift.im/impromptu"); + invite.setIsImpromptu(!(impromptuNode instanceof NullParserElement)); } } diff --git a/src/com/isode/stroke/parser/payloadparsers/PrivateStorageParser.java b/src/com/isode/stroke/parser/payloadparsers/PrivateStorageParser.java new file mode 100644 index 0000000..df62938 --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/PrivateStorageParser.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.parser.payloadparsers; + +import com.isode.stroke.elements.PrivateStorage; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; +import com.isode.stroke.parser.PayloadParser; +import com.isode.stroke.parser.PayloadParserFactory; +import com.isode.stroke.parser.PayloadParserFactoryCollection; + +public class PrivateStorageParser extends GenericPayloadParser<PrivateStorage> { + + private PayloadParserFactoryCollection factories; + private int level; + private PayloadParser currentPayloadParser; + + public PrivateStorageParser(PayloadParserFactoryCollection factories) { + super(new PrivateStorage()); + this.factories = factories; + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + if (level == 1) { + PayloadParserFactory payloadParserFactory = factories.getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory != null) { + currentPayloadParser = payloadParserFactory.createPayloadParser(); + } + } + + if (level >= 1 && currentPayloadParser != null) { + currentPayloadParser.handleStartElement(element, ns, attributes); + } + ++level; + } + + public void handleEndElement(String element, String ns) { + --level; + if (currentPayloadParser != null) { + if (level >= 1) { + currentPayloadParser.handleEndElement(element, ns); + } + + if (level == 1) { + getPayloadInternal().setPayload(currentPayloadParser.getPayload()); + } + } + } + + public void handleCharacterData(String data) { + if (level > 1 && currentPayloadParser != null) { + currentPayloadParser.handleCharacterData(data); + } + } +} diff --git a/src/com/isode/stroke/parser/payloadparsers/PrivateStorageParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/PrivateStorageParserFactory.java new file mode 100644 index 0000000..b2b3db0 --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/PrivateStorageParserFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2015 Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.parser.payloadparsers; + +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.PayloadParser; +import com.isode.stroke.parser.PayloadParserFactory; +import com.isode.stroke.parser.PayloadParserFactoryCollection; + +public class PrivateStorageParserFactory implements PayloadParserFactory { + + private PayloadParserFactoryCollection factories; + + public PrivateStorageParserFactory(PayloadParserFactoryCollection factories) { + this.factories = factories; + } + + @Override + public boolean canParse(String element, String ns, AttributeMap map) { + return "query".equals(element) && "jabber:iq:private".equals(ns); + } + + @Override + public PayloadParser createPayloadParser() { + return new PrivateStorageParser(factories); + } +} diff --git a/src/com/isode/stroke/parser/payloadparsers/PubSubEventParser.java b/src/com/isode/stroke/parser/payloadparsers/PubSubEventParser.java index d7bdbe6..f1f7701 100644 --- a/src/com/isode/stroke/parser/payloadparsers/PubSubEventParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/PubSubEventParser.java @@ -1,9 +1,5 @@ /* -* Copyright (c) 2014, Isode Limited, London, England. -* All rights reserved. -*/ -/* -* Copyright (c) 2014, Remko Tronçon. +* Copyright (c) 2013-2015, Isode Limited, London, England. * All rights reserved. */ @@ -25,22 +21,22 @@ public PubSubEventParser(PayloadParserFactoryCollection parser) { @Override public void handleStartElement(String element, String ns, AttributeMap attributes) { if (level_ == 1) { - if (element == "items" && ns == "http://jabber.org/protocol/pubsub#event") { + if ("items".equals(element) && "http://jabber.org/protocol/pubsub#event".equals(ns)) { currentPayloadParser_ = new PubSubEventItemsParser(parsers_); } - if (element == "collection" && ns == "http://jabber.org/protocol/pubsub#event") { + if ("collection".equals(element) && "http://jabber.org/protocol/pubsub#event".equals(ns)) { currentPayloadParser_ = new PubSubEventCollectionParser(parsers_); } - if (element == "purge" && ns == "http://jabber.org/protocol/pubsub#event") { + if ("purge".equals(element) && "http://jabber.org/protocol/pubsub#event".equals(ns)) { currentPayloadParser_ = new PubSubEventPurgeParser(parsers_); } - if (element == "configuration" && ns == "http://jabber.org/protocol/pubsub#event") { + if ("configuration".equals(element) && "http://jabber.org/protocol/pubsub#event".equals(ns)) { currentPayloadParser_ = new PubSubEventConfigurationParser(parsers_); } - if (element == "delete" && ns == "http://jabber.org/protocol/pubsub#event") { + if ("delete".equals(element) && "http://jabber.org/protocol/pubsub#event".equals(ns)) { currentPayloadParser_ = new PubSubEventDeleteParser(parsers_); } - if (element == "subscription" && ns == "http://jabber.org/protocol/pubsub#event") { + if ("subscription".equals(element) && "http://jabber.org/protocol/pubsub#event".equals(ns)) { currentPayloadParser_ = new PubSubEventSubscriptionParser(parsers_); } } diff --git a/src/com/isode/stroke/parser/PubSubOwnerPubSubParser.java b/src/com/isode/stroke/parser/payloadparsers/PubSubOwnerPubSubParser.java index ac0c4fe..d711cf1 100644 --- a/src/com/isode/stroke/parser/PubSubOwnerPubSubParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/PubSubOwnerPubSubParser.java @@ -1,16 +1,16 @@ /* - * Copyright (c) 2014, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2014, Remko Tronçon. + * Copyright (c) 2013-2015, Isode Limited, London, England. * All rights reserved. */ -package com.isode.stroke.parser; +package com.isode.stroke.parser.payloadparsers; import com.isode.stroke.elements.PubSubOwnerPayload; import com.isode.stroke.elements.PubSubOwnerPubSub; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; +import com.isode.stroke.parser.PayloadParser; +import com.isode.stroke.parser.PayloadParserFactoryCollection; import com.isode.stroke.parser.payloadparsers.PubSubOwnerAffiliationsParser; import com.isode.stroke.parser.payloadparsers.PubSubOwnerConfigureParser; import com.isode.stroke.parser.payloadparsers.PubSubOwnerDefaultParser; @@ -30,30 +30,30 @@ public class PubSubOwnerPubSubParser extends public void handleStartElement(String element, String ns, AttributeMap attributes) { if (level_ == 1) { - if (element == "configure" - && ns == "http://jabber.org/protocol/pubsub#owner") { + if ("configure".equals(element) + && "http://jabber.org/protocol/pubsub#owner".equals(ns)) { currentPayloadParser_ = new PubSubOwnerConfigureParser(parsers_); } - if (element == "subscriptions" - && ns == "http://jabber.org/protocol/pubsub#owner") { + if ("subscriptions".equals(element) + && "http://jabber.org/protocol/pubsub#owner".equals(ns)) { currentPayloadParser_ = new PubSubOwnerSubscriptionsParser( parsers_); } - if (element == "default" - && ns == "http://jabber.org/protocol/pubsub#owner") { + if ("default".equals(element) + && "http://jabber.org/protocol/pubsub#owner".equals(ns)) { currentPayloadParser_ = new PubSubOwnerDefaultParser(parsers_); } - if (element == "purge" - && ns == "http://jabber.org/protocol/pubsub#owner") { + if ("purge".equals(element) + && "http://jabber.org/protocol/pubsub#owner".equals(ns)) { currentPayloadParser_ = new PubSubOwnerPurgeParser(parsers_); } - if (element == "affiliations" - && ns == "http://jabber.org/protocol/pubsub#owner") { + if ("affiliations".equals(element) + && "http://jabber.org/protocol/pubsub#owner".equals(ns)) { currentPayloadParser_ = new PubSubOwnerAffiliationsParser( parsers_); } - if (element == "delete" - && ns == "http://jabber.org/protocol/pubsub#owner") { + if ("delete".equals(element) + && "http://jabber.org/protocol/pubsub#owner".equals(ns)) { currentPayloadParser_ = new PubSubOwnerDeleteParser(parsers_); } } diff --git a/src/com/isode/stroke/parser/payloadparsers/PubSubParser.java b/src/com/isode/stroke/parser/payloadparsers/PubSubParser.java index 134d654..8497555 100644 --- a/src/com/isode/stroke/parser/payloadparsers/PubSubParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/PubSubParser.java @@ -1,9 +1,5 @@ /* -* Copyright (c) 2014, Isode Limited, London, England. -* All rights reserved. -*/ -/* -* Copyright (c) 2014, Remko Tronçon. +* Copyright (c) 2013-2015, Isode Limited, London, England. * All rights reserved. */ @@ -30,40 +26,40 @@ public class PubSubParser extends GenericPayloadParser<PubSub> { public void handleStartElement(String element, String ns, AttributeMap attributes) { if (level_ == 1) { - if (element == "items" && ns == "http://jabber.org/protocol/pubsub") { + if ("items".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubItemsParser(parsers_); } - if (element == "create" && ns == "http://jabber.org/protocol/pubsub") { + if ("create".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubCreateParser(parsers_); } - if (element == "publish" && ns == "http://jabber.org/protocol/pubsub") { + if ("publish".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubPublishParser(parsers_); } - if (element == "affiliations" && ns == "http://jabber.org/protocol/pubsub") { + if ("affiliations".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubAffiliationsParser(parsers_); } - if (element == "retract" && ns == "http://jabber.org/protocol/pubsub") { + if ("retract".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubRetractParser(parsers_); } - if (element == "options" && ns == "http://jabber.org/protocol/pubsub") { + if ("options".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubOptionsParser(parsers_); } - if (element == "configure" && ns == "http://jabber.org/protocol/pubsub") { + if ("configure".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubConfigureParser(parsers_); } - if (element == "default" && ns == "http://jabber.org/protocol/pubsub") { + if ("default".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubDefaultParser(parsers_); } - if (element == "subscriptions" && ns == "http://jabber.org/protocol/pubsub") { + if ("subscriptions".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubSubscriptionsParser(parsers_); } - if (element == "subscribe" && ns == "http://jabber.org/protocol/pubsub") { + if ("subscribe".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubSubscribeParser(parsers_); } - if (element == "unsubscribe" && ns == "http://jabber.org/protocol/pubsub") { + if ("unsubscribe".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubUnsubscribeParser(parsers_); } - if (element == "subscription" && ns == "http://jabber.org/protocol/pubsub") { + if ("subscription".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { currentPayloadParser_ = new PubSubSubscriptionParser(parsers_); } } @@ -83,10 +79,10 @@ public class PubSubParser extends GenericPayloadParser<PubSub> { if (level_ == 1) { if (currentPayloadParser_ != null) { - if (element == "options" && ns == "http://jabber.org/protocol/pubsub") { + if ("options".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { optionsPayload_ = (PubSubOptions)currentPayloadParser_.getPayload(); } - else if (element == "configure" && ns == "http://jabber.org/protocol/pubsub") { + else if ("configure".equals(element) && "http://jabber.org/protocol/pubsub".equals(ns)) { configurePayload_ = (PubSubConfigure)currentPayloadParser_.getPayload(); } else { diff --git a/src/com/isode/stroke/parser/payloadparsers/ReplaceParser.java b/src/com/isode/stroke/parser/payloadparsers/ReplaceParser.java new file mode 100644 index 0000000..601ab7d --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/ReplaceParser.java @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2014-2015, Isode Limited, London, England. +* All rights reserved. +*/ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.parser.payloadparsers; + +import com.isode.stroke.elements.Replace; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; + +public class ReplaceParser extends GenericPayloadParser<Replace> { + + public ReplaceParser() { + super(new Replace()); + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + if (level_ == 0) { + String id = attributes.getAttribute("id"); + getPayloadInternal().setID(id); + } + ++level_; + } + + public void handleEndElement(String element, String ns) { + --level_; + } + + public void handleCharacterData(String data) { + } + + private int level_; +} diff --git a/src/com/isode/stroke/parser/payloadparsers/ResultSetParser.java b/src/com/isode/stroke/parser/payloadparsers/ResultSetParser.java index 26343a0..628c522 100644 --- a/src/com/isode/stroke/parser/payloadparsers/ResultSetParser.java +++ b/src/com/isode/stroke/parser/payloadparsers/ResultSetParser.java @@ -1,10 +1,5 @@ /* -* Copyright (c) 2014 Kevin Smith and Remko Tronçon -* All rights reserved. -*/ - -/* -* Copyright (c) 2014, Isode Limited, London, England. +* Copyright (c) 2014-2015, Isode Limited, London, England. * All rights reserved. */ @@ -23,7 +18,7 @@ public class ResultSetParser extends GenericPayloadParser<ResultSet> { public void handleStartElement(String element, String ns, AttributeMap attributes) { currentText_ = ""; if (level_ == 1) { - if (element == "first" && ns == "http://jabber.org/protocol/rsm") { + if ("first".equals(element) && "http://jabber.org/protocol/rsm".equals(ns)) { String attributeValue = attributes.getAttributeValue("index"); if (attributeValue != null) { getPayloadInternal().setFirstIDIndex(Long.parseLong(attributeValue)); @@ -36,17 +31,17 @@ public class ResultSetParser extends GenericPayloadParser<ResultSet> { public void handleEndElement(String element, String ns) { --level_; if (level_ == 1) { - if (element == "max") { + if ("max".equals(element)) { getPayloadInternal().setMaxItems(Long.parseLong(currentText_)); - } else if (element == "count") { + } else if ("count".equals(element)) { getPayloadInternal().setCount(Long.parseLong(currentText_)); - } else if (element == "first") { + } else if ("first".equals(element)) { getPayloadInternal().setFirstID(currentText_); - } else if (element == "last") { + } else if ("last".equals(element)) { getPayloadInternal().setLastID(currentText_); - } else if (element == "after") { + } else if ("after".equals(element)) { getPayloadInternal().setAfter(currentText_); - } else if (element == "before") { + } else if ("before".equals(element)) { getPayloadInternal().setBefore(currentText_); } } diff --git a/src/com/isode/stroke/parser/payloadparsers/StatusParser.java b/src/com/isode/stroke/parser/payloadparsers/StatusParser.java new file mode 100644 index 0000000..5a463ef --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/StatusParser.java @@ -0,0 +1,35 @@ +/* +* Copyright (c) 2010-2015, Isode Limited, London, England. +* All rights reserved. +*/ + +package com.isode.stroke.parser.payloadparsers; + +import com.isode.stroke.elements.Status; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; + +public class StatusParser extends GenericPayloadParser<Status> { + + public StatusParser() { + super(new Status()); + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + ++level_; + } + + public void handleEndElement(String element, String ns) { + --level_; + if (level_ == 0) { + getPayloadInternal().setText(text_); + } + } + + public void handleCharacterData(String data) { + text_ += data; + } + + private int level_; + private String text_ = ""; +} diff --git a/src/com/isode/stroke/parser/payloadparsers/StatusShowParser.java b/src/com/isode/stroke/parser/payloadparsers/StatusShowParser.java new file mode 100644 index 0000000..abe8692 --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/StatusShowParser.java @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2010-2015, Isode Limited, London, England. +* All rights reserved. +*/ + +package com.isode.stroke.parser.payloadparsers; + +import com.isode.stroke.elements.StatusShow; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; + +public class StatusShowParser extends GenericPayloadParser<StatusShow> { + + public StatusShowParser() { + super(new StatusShow()); + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + ++level_; + } + + public void handleEndElement(String element, String ns) { + --level_; + if (level_ == 0) { + if ("away".equals(text_)) { + getPayloadInternal().setType(StatusShow.Type.Away); + } + else if ("chat".equals(text_)) { + getPayloadInternal().setType(StatusShow.Type.FFC); + } + else if ("xa".equals(text_)) { + getPayloadInternal().setType(StatusShow.Type.XA); + } + else if ("dnd".equals(text_)) { + getPayloadInternal().setType(StatusShow.Type.DND); + } + else { + getPayloadInternal().setType(StatusShow.Type.Online); + } + } + } + + public void handleCharacterData(String data) { + text_ = text_ == null ? data : text_ + data; + } + + private int level_; + private String text_; +} diff --git a/src/com/isode/stroke/parser/payloadparsers/StorageParser.java b/src/com/isode/stroke/parser/payloadparsers/StorageParser.java new file mode 100644 index 0000000..7898938 --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/StorageParser.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.parser.payloadparsers; + +import com.isode.stroke.elements.Storage; +import com.isode.stroke.elements.Storage.Room; +import com.isode.stroke.elements.Storage.URL; +import com.isode.stroke.jid.JID; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; + +public class StorageParser extends GenericPayloadParser<Storage> { + + private final static int BookmarkLevel = 1; + private static final int DetailLevel = 2; + private int level; + private String currentText; + private Room room; + private URL url; + + public StorageParser() { + super(new Storage()); + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + if (level == BookmarkLevel) { + if ("conference".equals(element)) { + assert(room == null); + room = new Storage.Room(); + room.autoJoin = attributes.getBoolAttribute("autojoin", false); + room.jid = new JID(attributes.getAttribute("jid")); + room.name = attributes.getAttribute("name"); + } + else if ("url".equals(element)) { + assert(url == null); + url = new Storage.URL(); + url.name = attributes.getAttribute("name"); + url.url = attributes.getAttribute("url"); + } + } + else if (level == DetailLevel) { + currentText = ""; + } + ++level; + } + + public void handleEndElement(String element, String ns) { + --level; + if (level == BookmarkLevel) { + if ("conference".equals(element)) { + assert(room != null); + getPayloadInternal().addRoom(room); + room = null; + } + else if ("url".equals(element)) { + assert(url != null); + getPayloadInternal().addURL(url); + url = null; + } + } + else if (level == DetailLevel && room != null) { + if ("nick".equals(element)) { + room.nick = currentText; + } + else if ("password".equals(element)) { + room.password = currentText; + } + } + } + + public void handleCharacterData(String data) { + currentText += data; + } +} diff --git a/src/com/isode/stroke/parser/payloadparsers/SubjectParser.java b/src/com/isode/stroke/parser/payloadparsers/SubjectParser.java new file mode 100644 index 0000000..5aa69c5 --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/SubjectParser.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.parser.payloadparsers; + +import com.isode.stroke.elements.Subject; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; + +public class SubjectParser extends GenericPayloadParser<Subject> { + + + private int level_; + private String text_ = ""; + + public SubjectParser() { + super(new Subject()); + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + ++level_; + } + + public void handleEndElement(String element, String ns) { + --level_; + if (level_ == 0) { + getPayloadInternal().setText(text_); + } + } + + public void handleCharacterData(String data) { + text_ += data; + } +} diff --git a/src/com/isode/stroke/parser/payloadparsers/VCardParser.java b/src/com/isode/stroke/parser/payloadparsers/VCardParser.java new file mode 100644 index 0000000..b955dc4 --- /dev/null +++ b/src/com/isode/stroke/parser/payloadparsers/VCardParser.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.parser.payloadparsers; + +import java.util.Stack; + +import com.isode.stroke.base.DateTime; +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.GenericPayloadParser; +import com.isode.stroke.parser.SerializingParser; +import com.isode.stroke.stringcodecs.Base64; + +public class VCardParser extends GenericPayloadParser<VCard> { + + Stack<String> elementStack_ = new Stack<String>(); + VCard.EMailAddress currentEMailAddress_; + VCard.Telephone currentTelephone_; + VCard.Address currentAddress_; + VCard.AddressLabel currentAddressLabel_; + VCard.Organization currentOrganization_; + SerializingParser unknownContentParser_; + String currentText_ = ""; + + public VCardParser() { + super(new VCard()); + } + + public void handleStartElement(String element, String ns, AttributeMap attributes) { + elementStack_.add(element); + String elementHierarchy = getElementHierarchy(); + if ("/vCard/EMAIL".equals(elementHierarchy)) { + currentEMailAddress_ = new VCard.EMailAddress(); + } + if ("/vCard/TEL".equals(elementHierarchy)) { + currentTelephone_ = new VCard.Telephone(); + } + if ("/vCard/ADR".equals(elementHierarchy)) { + currentAddress_ = new VCard.Address(); + } + if ("/vCard/LABEL".equals(elementHierarchy)) { + currentAddressLabel_ = new VCard.AddressLabel(); + } + if ("/vCard/ORG".equals(elementHierarchy)) { + currentOrganization_ = new VCard.Organization(); + } + if (elementStack_.size() == 2) { + assert(unknownContentParser_ == null); + unknownContentParser_ = new SerializingParser(); + unknownContentParser_.handleStartElement(element, ns, attributes); + } + else if (unknownContentParser_ != null) { + unknownContentParser_.handleStartElement(element, ns, attributes); + } + + currentText_ = ""; + } + + public void handleEndElement(String element, String ns) { + if (unknownContentParser_ != null) { + unknownContentParser_.handleEndElement(element, ns); + } + + String elementHierarchy = getElementHierarchy(); + if ("/vCard/VERSION".equals(elementHierarchy)) { + getPayloadInternal().setVersion(currentText_); + } + else if ("/vCard/FN".equals(elementHierarchy)) { + getPayloadInternal().setFullName(currentText_); + } + else if ("/vCard/N/FAMILY".equals(elementHierarchy)) { + getPayloadInternal().setFamilyName(currentText_); + } + else if ("/vCard/N/GIVEN".equals(elementHierarchy)) { + getPayloadInternal().setGivenName(currentText_); + } + else if ("/vCard/N/MIDDLE".equals(elementHierarchy)) { + getPayloadInternal().setMiddleName(currentText_); + } + else if ("/vCard/N/PREFIX".equals(elementHierarchy)) { + getPayloadInternal().setPrefix(currentText_); + } + else if ("/vCard/N/SUFFIX".equals(elementHierarchy)) { + getPayloadInternal().setSuffix(currentText_); + } + else if ("/vCard/N".equals(elementHierarchy)) { + } + else if ("/vCard/NICKNAME".equals(elementHierarchy)) { + getPayloadInternal().setNickname(currentText_); + } + else if ("/vCard/PHOTO/TYPE".equals(elementHierarchy)) { + getPayloadInternal().setPhotoType(currentText_); + } + else if ("/vCard/PHOTO/BINVAL".equals(elementHierarchy)) { + getPayloadInternal().setPhoto(Base64.decode(currentText_.replace("\n", "").replace("\r", ""))); + } + else if ("/vCard/PHOTO".equals(elementHierarchy)) { + } + else if ("/vCard/EMAIL/USERID".equals(elementHierarchy)) { + currentEMailAddress_.address = currentText_; + } + else if ("/vCard/EMAIL/HOME".equals(elementHierarchy)) { + currentEMailAddress_.isHome = true; + } + else if ("/vCard/EMAIL/WORK".equals(elementHierarchy)) { + currentEMailAddress_.isWork = true; + } + else if ("/vCard/EMAIL/INTERNET".equals(elementHierarchy)) { + currentEMailAddress_.isInternet = true; + } + else if ("/vCard/EMAIL/X400".equals(elementHierarchy)) { + currentEMailAddress_.isX400 = true; + } + else if ("/vCard/EMAIL/PREF".equals(elementHierarchy)) { + currentEMailAddress_.isPreferred = true; + } + else if ("/vCard/EMAIL".equals(elementHierarchy) && currentEMailAddress_.address != null && !currentEMailAddress_.address.isEmpty()) { + getPayloadInternal().addEMailAddress(currentEMailAddress_); + } + else if ("/vCard/BDAY".equals(elementHierarchy) && !currentText_.isEmpty()) { + getPayloadInternal().setBirthday(DateTime.stringToDate(currentText_)); + } + else if ("/vCard/TEL/NUMBER".equals(elementHierarchy)) { + currentTelephone_.number = currentText_; + } + else if ("/vCard/TEL/HOME".equals(elementHierarchy)) { + currentTelephone_.isHome = true; + } + else if ("/vCard/TEL/WORK".equals(elementHierarchy)) { + currentTelephone_.isWork = true; + } + else if ("/vCard/TEL/VOICE".equals(elementHierarchy)) { + currentTelephone_.isVoice = true; + } + else if ("/vCard/TEL/FAX".equals(elementHierarchy)) { + currentTelephone_.isFax = true; + } + else if ("/vCard/TEL/PAGER".equals(elementHierarchy)) { + currentTelephone_.isPager = true; + } + else if ("/vCard/TEL/MSG".equals(elementHierarchy)) { + currentTelephone_.isMSG = true; + } + else if ("/vCard/TEL/CELL".equals(elementHierarchy)) { + currentTelephone_.isCell = true; + } + else if ("/vCard/TEL/VIDEO".equals(elementHierarchy)) { + currentTelephone_.isVideo = true; + } + else if ("/vCard/TEL/BBS".equals(elementHierarchy)) { + currentTelephone_.isBBS = true; + } + else if ("/vCard/TEL/MODEM".equals(elementHierarchy)) { + currentTelephone_.isModem = true; + } + else if ("/vCard/TEL/ISDN".equals(elementHierarchy)) { + currentTelephone_.isISDN = true; + } + else if ("/vCard/TEL/PCS".equals(elementHierarchy)) { + currentTelephone_.isPCS = true; + } + else if ("/vCard/TEL/PREF".equals(elementHierarchy)) { + currentTelephone_.isPreferred = true; + } + else if ("/vCard/TEL".equals(elementHierarchy) && currentTelephone_.number != null && !currentTelephone_.number.isEmpty()) { + getPayloadInternal().addTelephone(currentTelephone_); + } + else if ("/vCard/ADR/HOME".equals(elementHierarchy)) { + currentAddress_.isHome = true; + } + else if ("/vCard/ADR/WORK".equals(elementHierarchy)) { + currentAddress_.isWork = true; + } + else if ("/vCard/ADR/POSTAL".equals(elementHierarchy)) { + currentAddress_.isPostal = true; + } + else if ("/vCard/ADR/PARCEL".equals(elementHierarchy)) { + currentAddress_.isParcel = true; + } + else if ("/vCard/ADR/DOM".equals(elementHierarchy)) { + currentAddress_.deliveryType = VCard.DeliveryType.DomesticDelivery; + } + else if ("/vCard/ADR/INTL".equals(elementHierarchy)) { + currentAddress_.deliveryType = VCard.DeliveryType.InternationalDelivery; + } + else if ("/vCard/ADR/PREF".equals(elementHierarchy)) { + currentAddress_.isPreferred = true; + } + else if ("/vCard/ADR/POBOX".equals(elementHierarchy)) { + currentAddress_.poBox = currentText_; + } + else if ("/vCard/ADR/EXTADD".equals(elementHierarchy)) { + currentAddress_.addressExtension = currentText_; + } + else if ("/vCard/ADR/STREET".equals(elementHierarchy)) { + currentAddress_.street = currentText_; + } + else if ("/vCard/ADR/LOCALITY".equals(elementHierarchy)) { + currentAddress_.locality = currentText_; + } + else if ("/vCard/ADR/REGION".equals(elementHierarchy)) { + currentAddress_.region = currentText_; + } + else if ("/vCard/ADR/PCODE".equals(elementHierarchy)) { + currentAddress_.postalCode = currentText_; + } + else if ("/vCard/ADR/CTRY".equals(elementHierarchy)) { + currentAddress_.country = currentText_; + } + else if ("/vCard/ADR".equals(elementHierarchy)) { + if (currentAddress_.poBox != null && !currentAddress_.poBox.isEmpty() + || currentAddress_.addressExtension != null && !currentAddress_.addressExtension.isEmpty() + || currentAddress_.street != null && !currentAddress_.street.isEmpty() + || currentAddress_.locality != null && !currentAddress_.locality.isEmpty() + || currentAddress_.region != null && !currentAddress_.region.isEmpty() + || currentAddress_.postalCode != null && !currentAddress_.postalCode.isEmpty() + || currentAddress_.country != null && !currentAddress_.country.isEmpty()) { + getPayloadInternal().addAddress(currentAddress_); + } + } + else if ("/vCard/LABEL/HOME".equals(elementHierarchy)) { + currentAddressLabel_.isHome = true; + } + else if ("/vCard/LABEL/WORK".equals(elementHierarchy)) { + currentAddressLabel_.isWork = true; + } + else if ("/vCard/LABEL/POSTAL".equals(elementHierarchy)) { + currentAddressLabel_.isPostal = true; + } + else if ("/vCard/LABEL/PARCEL".equals(elementHierarchy)) { + currentAddressLabel_.isParcel = true; + } + else if ("/vCard/LABEL/DOM".equals(elementHierarchy)) { + currentAddressLabel_.deliveryType = VCard.DeliveryType.DomesticDelivery; + } + else if ("/vCard/LABEL/INTL".equals(elementHierarchy)) { + currentAddressLabel_.deliveryType = VCard.DeliveryType.InternationalDelivery; + } + else if ("/vCard/LABEL/PREF".equals(elementHierarchy)) { + currentAddressLabel_.isPreferred = true; + } + else if ("/vCard/LABEL/LINE".equals(elementHierarchy)) { + currentAddressLabel_.lines.add(currentText_); + } + else if ("/vCard/LABEL".equals(elementHierarchy)) { + getPayloadInternal().addAddressLabel(currentAddressLabel_); + } + else if ("/vCard/JID".equals(elementHierarchy) && !currentText_.isEmpty()) { + getPayloadInternal().addJID(new JID(currentText_)); + } + else if ("/vCard/DESC".equals(elementHierarchy)) { + getPayloadInternal().setDescription(currentText_); + } + else if ("/vCard/ORG/ORGNAME".equals(elementHierarchy)) { + currentOrganization_.name = currentText_; + } + else if ("/vCard/ORG/ORGUNIT".equals(elementHierarchy) && !currentText_.isEmpty()) { + currentOrganization_.units.add(currentText_); + } + else if ("/vCard/ORG".equals(elementHierarchy)) { + if (!currentOrganization_.name.isEmpty() || !currentOrganization_.units.isEmpty()) { + getPayloadInternal().addOrganization(currentOrganization_); + } + } + else if ("/vCard/TITLE".equals(elementHierarchy) && !currentText_.isEmpty()) { + getPayloadInternal().addTitle(currentText_); + } + else if ("/vCard/ROLE".equals(elementHierarchy) && !currentText_.isEmpty()) { + getPayloadInternal().addRole(currentText_); + } + else if ("/vCard/URL".equals(elementHierarchy) && !currentText_.isEmpty()) { + getPayloadInternal().addURL(currentText_); + } + else if (elementStack_.size() == 2 && unknownContentParser_ != null) { + getPayloadInternal().addUnknownContent(unknownContentParser_.getResult()); + } + + if (elementStack_.size() == 2 && unknownContentParser_ != null) { + unknownContentParser_ = null; + } + elementStack_.pop(); + } + + public void handleCharacterData(String text) { + if (unknownContentParser_ != null) { + unknownContentParser_.handleCharacterData(text); + } + currentText_ += text; + } + + private String getElementHierarchy() { + String result = ""; + for(String element : elementStack_) { + result += "/" + element; + } + return result; + } +} diff --git a/src/com/isode/stroke/presence/PayloadAddingPresenceSender.java b/src/com/isode/stroke/presence/PayloadAddingPresenceSender.java new file mode 100644 index 0000000..ed6106b --- /dev/null +++ b/src/com/isode/stroke/presence/PayloadAddingPresenceSender.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.presence; + +import com.isode.stroke.elements.Payload; +import com.isode.stroke.elements.Presence; + +public class PayloadAddingPresenceSender implements PresenceSender { + private Presence lastSentPresence; + private final PresenceSender sender; + private Payload payload; + + public PayloadAddingPresenceSender(PresenceSender sender) { + this.sender = sender; + } + + public void sendPresence(Presence presence) { + if (presence.isAvailable()) { + if (presence.getTo() != null && !presence.getTo().isValid()) { + lastSentPresence = presence; + } + } else { + lastSentPresence = null; + } + if (payload != null) { + Presence sentPresence = presence; + sentPresence.updatePayload(payload); + sender.sendPresence(sentPresence); + } else { + sender.sendPresence(presence); + } + } + + public boolean isAvailable() { + return sender.isAvailable(); + } + + /** + * Sets the payload to be added to outgoing presences. If initial presence + * has been sent, this will resend the last sent presence with an updated + * payload. Initial presence is reset when unavailable presence is sent, or + * when reset() is called. + */ + public void setPayload(Payload payload) { + this.payload = payload; + if (lastSentPresence != null) { + sendPresence(lastSentPresence); + } + } + + /** + * Resets the presence sender. This puts the presence sender back in the + * initial state (before initial presence has been sent). This also resets + * the chained sender. + */ + public void reset() { + lastSentPresence = null; + } + +} diff --git a/src/com/isode/stroke/presence/PresenceOracle.java b/src/com/isode/stroke/presence/PresenceOracle.java new file mode 100644 index 0000000..8d63f59 --- /dev/null +++ b/src/com/isode/stroke/presence/PresenceOracle.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.presence; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.isode.stroke.client.StanzaChannel; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.elements.StatusShow; +import com.isode.stroke.jid.JID; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; + +public class PresenceOracle { + private final Map<JID,Map<JID,Presence>> entries_ = new HashMap<JID,Map<JID,Presence>>(); + private final StanzaChannel stanzaChannel_; + private final SignalConnection onPresenceReceivedSignal; + private final SignalConnection onAvailableChangedSignal; + + + public final Signal1<Presence> onPresenceChange = new Signal1<Presence>(); + + public PresenceOracle(StanzaChannel stanzaChannel) { + stanzaChannel_ = stanzaChannel; + onPresenceReceivedSignal = stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() { + @Override + public void call(Presence p1) { + handleIncomingPresence(p1); + } + }); + onAvailableChangedSignal = stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() { + @Override + public void call(Boolean p1) { + handleStanzaChannelAvailableChanged(p1); + } + }); + } + + void delete() { + onPresenceReceivedSignal.disconnect(); + onAvailableChangedSignal.disconnect(); + } + + void handleStanzaChannelAvailableChanged(boolean available) { + if (available) { + entries_.clear(); + } + } + + + void handleIncomingPresence(Presence presence) { + JID bareJID = presence.getFrom().toBare(); + if (presence.getType() == Presence.Type.Subscribe) { + } + else { + Presence passedPresence = presence; + if (presence.getType() == Presence.Type.Unsubscribe) { + /* 3921bis says that we don't follow up with an unavailable, so simulate this ourselves */ + passedPresence = new Presence(); + passedPresence.setType(Presence.Type.Unavailable); + passedPresence.setFrom(bareJID); + passedPresence.setStatus(presence.getStatus()); + } + Map<JID,Presence> jidMap = entries_.get(bareJID); + if (jidMap == null) jidMap = new HashMap<JID,Presence>(); + if (passedPresence.getFrom().isBare() && presence.getType() == Presence.Type.Unavailable) { + /* Have a bare-JID only presence of offline */ + jidMap.clear(); + } else if (passedPresence.getType() == Presence.Type.Available) { + /* Don't have a bare-JID only offline presence once there are available presences */ + jidMap.remove(bareJID); + } + if (passedPresence.getType() == Presence.Type.Unavailable && jidMap.size() > 1) { + jidMap.remove(passedPresence.getFrom()); + } else { + jidMap.put(passedPresence.getFrom(), passedPresence); + } + entries_.put(bareJID, jidMap); + onPresenceChange.emit(passedPresence); + } + } + + public Presence getLastPresence(final JID jid) { + Map<JID,Presence> presenceMap = entries_.get(jid.toBare()); + if (presenceMap == null) return new Presence(); + + Presence i = presenceMap.get(jid); + if (i != null) { + return i; + } else { + return new Presence(); + } + } + + public Collection<Presence> getAllPresence(final JID bareJID) { + Collection<Presence> results = new ArrayList<Presence>(); + + Map<JID,Presence> presenceMap = entries_.get(bareJID); + if (presenceMap == null) return results; + + results.addAll(presenceMap.values()); + return results; + } + + public Presence getHighestPriorityPresence(final JID bareJID) { + Map<JID,Presence> presenceMap = entries_.get(bareJID); + if (presenceMap == null) return new Presence(); + + Presence highest = null; + for (Presence current : presenceMap.values()) { + if (highest == null + || current.getPriority() > highest.getPriority() + || (current.getPriority() == highest.getPriority() + && StatusShow.typeToAvailabilityOrdering(current.getShow()) > StatusShow.typeToAvailabilityOrdering(highest.getShow()))) { + highest = current; + } + + } + return highest; + } +} diff --git a/src/com/isode/stroke/presence/SubscriptionManager.java b/src/com/isode/stroke/presence/SubscriptionManager.java new file mode 100644 index 0000000..91075fc --- /dev/null +++ b/src/com/isode/stroke/presence/SubscriptionManager.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.presence; + +import com.isode.stroke.client.StanzaChannel; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.jid.JID; +import com.isode.stroke.signals.Signal2; +import com.isode.stroke.signals.Signal3; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; + +public class SubscriptionManager { + private StanzaChannel stanzaChannel; + private SignalConnection onPresenceReceivedConnection; + + public final Signal3<JID, String, Presence> onPresenceSubscriptionRequest = new Signal3<JID, String, Presence>(); + + public final Signal2<JID, String> onPresenceSubscriptionRevoked = new Signal2<JID, String>(); + + public SubscriptionManager(StanzaChannel channel) { + stanzaChannel = channel; + onPresenceReceivedConnection = stanzaChannel.onPresenceReceived.connect(new Slot1<Presence>() { + @Override + public void call(Presence p1) { + handleIncomingPresence(p1); + } + }); + } + + void delete() { + onPresenceReceivedConnection.disconnect(); + } + + public void cancelSubscription(final JID jid) { + Presence stanza = new Presence(); + stanza.setType(Presence.Type.Unsubscribed); + stanza.setTo(jid); + stanzaChannel.sendPresence(stanza); + } + + public void confirmSubscription(final JID jid) { + Presence stanza = new Presence(); + stanza.setType(Presence.Type.Subscribed); + stanza.setTo(jid); + stanzaChannel.sendPresence(stanza); + } + + + public void requestSubscription(final JID jid) { + Presence stanza = new Presence(); + stanza.setType(Presence.Type.Subscribe); + stanza.setTo(jid); + stanzaChannel.sendPresence(stanza); + } + + void handleIncomingPresence(Presence presence) { + JID bareJID = presence.getFrom().toBare(); + if (presence.getType() == Presence.Type.Subscribe) { + onPresenceSubscriptionRequest.emit(bareJID, presence.getStatus(), presence); + } + else if (presence.getType() == Presence.Type.Unsubscribe) { + onPresenceSubscriptionRevoked.emit(bareJID, presence.getStatus()); + } + } +} diff --git a/src/com/isode/stroke/queries/GetRosterRequest.java b/src/com/isode/stroke/queries/GetRosterRequest.java deleted file mode 100644 index 4501a94..0000000 --- a/src/com/isode/stroke/queries/GetRosterRequest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. - * All rights reserved. - */ - -package com.isode.stroke.queries; - -import com.isode.stroke.elements.IQ.Type; -import com.isode.stroke.elements.RosterPayload; -import com.isode.stroke.jid.JID; - -public class GetRosterRequest extends GenericRequest<RosterPayload> { - public GetRosterRequest(JID target, IQRouter iqRouter) { - super(Type.Get, target, new RosterPayload(), iqRouter); - } - - public GetRosterRequest(IQRouter iqRouter) { - super(Type.Get, new JID(), new RosterPayload(), iqRouter); - } -} diff --git a/src/com/isode/stroke/queries/IQRouter.java b/src/com/isode/stroke/queries/IQRouter.java index 409d02b..0dd55f2 100644 --- a/src/com/isode/stroke/queries/IQRouter.java +++ b/src/com/isode/stroke/queries/IQRouter.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010-2014, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.queries; @@ -63,7 +59,17 @@ public class IQRouter { return channel_.isAvailable(); } - private void handleIQ(IQ iq) { + /** + * Checks whether the given jid is the account JID (i.e. it is either + * the bare JID, or it is the empty JID). + * Can be used to check whether a stanza is sent by the server on behalf + * of the user's account. + */ + public boolean isAccountJID(final JID jid) { + return jid.isValid() ? jid_.toBare().compare(jid, JID.CompareType.WithResource) == 0 : true; + } + + private void handleIQ(IQ iq) { boolean handled = false; synchronized (handlers_) { for (IQHandler handler : handlers_) { diff --git a/src/com/isode/stroke/queries/SetResponder.java b/src/com/isode/stroke/queries/SetResponder.java new file mode 100644 index 0000000..c042385 --- /dev/null +++ b/src/com/isode/stroke/queries/SetResponder.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.queries; + +import com.isode.stroke.elements.Payload; +import com.isode.stroke.jid.JID; + +public abstract class SetResponder<T extends Payload> extends Responder<T> { + + public SetResponder(T payloadType, IQRouter router) { + super(payloadType, router); + } + + @Override + protected boolean handleGetRequest(JID from, JID to, String id, T payload) { + return false; + } +} diff --git a/src/com/isode/stroke/queries/requests/GetSecurityLabelsCatalogRequest.java b/src/com/isode/stroke/queries/requests/GetSecurityLabelsCatalogRequest.java new file mode 100644 index 0000000..fdadc9b --- /dev/null +++ b/src/com/isode/stroke/queries/requests/GetSecurityLabelsCatalogRequest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.queries.requests; + +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.SecurityLabelsCatalog; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.GenericRequest; +import com.isode.stroke.queries.IQRouter; + +public class GetSecurityLabelsCatalogRequest extends GenericRequest<SecurityLabelsCatalog>{ + + public static GetSecurityLabelsCatalogRequest create(JID recipient, IQRouter router) { + return new GetSecurityLabelsCatalogRequest(recipient, router); + } + + private GetSecurityLabelsCatalogRequest(JID recipient, IQRouter router) { + super(IQ.Type.Get, new JID(), new SecurityLabelsCatalog(recipient), router); + } +} diff --git a/src/com/isode/stroke/roster/GetRosterRequest.java b/src/com/isode/stroke/roster/GetRosterRequest.java new file mode 100644 index 0000000..18e885b --- /dev/null +++ b/src/com/isode/stroke/roster/GetRosterRequest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ + +package com.isode.stroke.roster; + +import com.isode.stroke.elements.IQ.Type; +import com.isode.stroke.elements.RosterPayload; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.GenericRequest; +import com.isode.stroke.queries.IQRouter; + +public class GetRosterRequest extends GenericRequest<RosterPayload> { + public GetRosterRequest(JID target, IQRouter iqRouter) { + super(Type.Get, target, new RosterPayload(), iqRouter); + } + + public GetRosterRequest(IQRouter iqRouter) { + super(Type.Get, new JID(), new RosterPayload(), iqRouter); + } + + public static GetRosterRequest create(IQRouter router) { + return new GetRosterRequest(router); + } + + public static GetRosterRequest create(IQRouter router, String version) { + GetRosterRequest request = new GetRosterRequest(router); + request.getPayloadGeneric().setVersion(version); + return request; + } +} diff --git a/src/com/isode/stroke/roster/RosterMemoryStorage.java b/src/com/isode/stroke/roster/RosterMemoryStorage.java new file mode 100644 index 0000000..9c6d55f --- /dev/null +++ b/src/com/isode/stroke/roster/RosterMemoryStorage.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2011-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import com.isode.stroke.elements.RosterPayload; + +public class RosterMemoryStorage implements RosterStorage { + private RosterPayload roster = new RosterPayload(); + + @Override + public RosterPayload getRoster() { + return roster; + } + + @Override + public void setRoster(RosterPayload r) { + roster = r; + } + +} diff --git a/src/com/isode/stroke/roster/RosterPushResponder.java b/src/com/isode/stroke/roster/RosterPushResponder.java new file mode 100644 index 0000000..18b20b5 --- /dev/null +++ b/src/com/isode/stroke/roster/RosterPushResponder.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.elements.RosterPayload; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.queries.SetResponder; +import com.isode.stroke.signals.Signal1; + +public class RosterPushResponder extends SetResponder<RosterPayload> { + + final Signal1<RosterPayload> onRosterReceived = new Signal1<RosterPayload>(); + + public RosterPushResponder(IQRouter router) { + super(new RosterPayload(), router); + } + + @Override + protected boolean handleSetRequest(JID from, JID to, String id, RosterPayload payload) { + if (getIQRouter().isAccountJID(from)) { + onRosterReceived.emit(payload); + sendResponse(from, id, new RosterPayload()); + } else { + sendError(from, id, ErrorPayload.Condition.NotAuthorized, ErrorPayload.Type.Cancel); + } + return true; + } + + +} diff --git a/src/com/isode/stroke/roster/RosterStorage.java b/src/com/isode/stroke/roster/RosterStorage.java new file mode 100644 index 0000000..df048cc --- /dev/null +++ b/src/com/isode/stroke/roster/RosterStorage.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import com.isode.stroke.elements.RosterPayload; + +public interface RosterStorage { + + RosterPayload getRoster(); + void setRoster(RosterPayload roster); +} diff --git a/src/com/isode/stroke/roster/SetRosterRequest.java b/src/com/isode/stroke/roster/SetRosterRequest.java new file mode 100644 index 0000000..4d52676 --- /dev/null +++ b/src/com/isode/stroke/roster/SetRosterRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.Payload; +import com.isode.stroke.elements.RosterPayload; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.queries.Request; +import com.isode.stroke.signals.Signal1; + +public class SetRosterRequest extends Request { + + static SetRosterRequest create(RosterPayload payload, IQRouter router) { + return new SetRosterRequest(new JID(), payload, router); + } + + static SetRosterRequest create(RosterPayload payload, final JID to, IQRouter router) { + return new SetRosterRequest(to, payload, router); + } + + private SetRosterRequest(final JID to, RosterPayload payload, IQRouter router) { + super(IQ.Type.Set, to, payload, router); + } + + public void handleResponse(Payload payload, ErrorPayload error) { + onResponse.emit(error); + } + + final Signal1<ErrorPayload> onResponse = new Signal1<ErrorPayload>(); + +} diff --git a/src/com/isode/stroke/roster/XMPPRoster.java b/src/com/isode/stroke/roster/XMPPRoster.java new file mode 100644 index 0000000..a2c3cd3 --- /dev/null +++ b/src/com/isode/stroke/roster/XMPPRoster.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import java.util.Set; +import java.util.Collection; + +import com.isode.stroke.elements.RosterItemPayload; +import com.isode.stroke.jid.JID; +import com.isode.stroke.signals.Signal; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.Signal3; + +public abstract class XMPPRoster { + /** + * Checks whether the bare jid of the given jid is in the roster. + */ + public abstract boolean containsJID(final JID jid); + + /** + * Retrieves the subscription state for the given jid. + */ + public abstract RosterItemPayload.Subscription getSubscriptionStateForJID(final JID jid); + + /** + * Retrieves the stored roster name for the given jid. + */ + public abstract String getNameForJID(final JID jid); + + /** + * Returns the list of groups for the given JID. + */ + public abstract Collection<String> getGroupsForJID(final JID jid); + + /** + * Retrieve the items in the roster. + */ + public abstract Collection<XMPPRosterItem> getItems(); + + /** + * Retrieve the item with the given JID. + */ + public abstract XMPPRosterItem getItem(final JID jid); + + /** + * Retrieve the list of (existing) groups. + */ + public abstract Set<String> getGroups(); + + /** + * Emitted when the given JID is added to the roster. + */ + public final Signal1<JID> onJIDAdded = new Signal1<JID>(); + + /** + * Emitted when the given JID is removed from the roster. + */ + public final Signal1<JID> onJIDRemoved = new Signal1<JID>(); + + /** + * Emitted when the name or the groups of the roster item with the + * given JID changes. + */ + public final Signal3<JID, String, Collection<String>> onJIDUpdated = new Signal3<JID, String, Collection<String>>(); + + /** + * Emitted when the roster is reset (e.g. due to logging in/logging out). + * After this signal is emitted, the roster is empty. It will be repopulated through + * onJIDAdded and onJIDRemoved events. + */ + public final Signal onRosterCleared = new Signal(); + + /** + * Emitted after the last contact of the initial roster request response + * was added. + */ + public final Signal onInitialRosterPopulated = new Signal(); +} diff --git a/src/com/isode/stroke/roster/XMPPRosterController.java b/src/com/isode/stroke/roster/XMPPRosterController.java new file mode 100644 index 0000000..6ad03f2 --- /dev/null +++ b/src/com/isode/stroke/roster/XMPPRosterController.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import java.util.Collection; + +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.elements.RosterItemPayload; +import com.isode.stroke.elements.RosterPayload; +import com.isode.stroke.roster.GetRosterRequest; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.Slot2; + +public class XMPPRosterController { + private IQRouter iqRouter_; + private RosterPushResponder rosterPushResponder_; + private XMPPRosterImpl xmppRoster_; + private RosterStorage rosterStorage_; + private boolean useVersioning; + + + /** + * The controller does not gain ownership of these parameters. + */ + public XMPPRosterController(IQRouter iqRouter, XMPPRosterImpl xmppRoster, RosterStorage rosterStorage) { + iqRouter_ = iqRouter; + rosterPushResponder_ = new RosterPushResponder(iqRouter); + xmppRoster_ = xmppRoster; + rosterStorage_ = rosterStorage; + useVersioning = false; + + rosterPushResponder_.onRosterReceived.connect(new Slot1<RosterPayload>() { + @Override + public void call(RosterPayload p1) { + handleRosterReceived(p1, false, new RosterPayload()); + } + }); + rosterPushResponder_.start(); + } + + public void delete() { + rosterPushResponder_.stop(); + } + + public void requestRoster() { + xmppRoster_.clear(); + + final RosterPayload storedRoster = rosterStorage_.getRoster(); + GetRosterRequest rosterRequest; + if (useVersioning) { + String version = ""; + if (storedRoster != null && storedRoster.getVersion() != null) { + version = storedRoster.getVersion(); + } + rosterRequest = GetRosterRequest.create(iqRouter_, version); + } + else { + rosterRequest = GetRosterRequest.create(iqRouter_); + } + + rosterRequest.onResponse.connect(new Slot2<RosterPayload, ErrorPayload>() { + @Override + public void call(RosterPayload p1, ErrorPayload p2) { + handleRosterReceived(p1, true, storedRoster); + } + }); + rosterRequest.send(); + } + + void handleRosterReceived(RosterPayload rosterPayload, boolean initial, RosterPayload previousRoster) { + if (rosterPayload != null) { + for (final RosterItemPayload item : rosterPayload.getItems()) { + //Don't worry about the updated case, the XMPPRoster sorts that out. + if (item.getSubscription() == RosterItemPayload.Subscription.Remove) { + xmppRoster_.removeContact(item.getJID()); + } else { + xmppRoster_.addContact(item.getJID(), item.getName(), item.getGroups(), item.getSubscription()); + } + } + } + else if (previousRoster != null) { + // The cached version hasn't changed; emit all items + for (final RosterItemPayload item : previousRoster.getItems()) { + if (item.getSubscription() != RosterItemPayload.Subscription.Remove) { + xmppRoster_.addContact(item.getJID(), item.getName(), item.getGroups(), item.getSubscription()); + } + else { + System.err.println("ERROR: Stored invalid roster item"); + } + } + } + if (initial) { + xmppRoster_.onInitialRosterPopulated.emit(); + } + if (rosterPayload != null && rosterPayload.getVersion() != null && useVersioning) { + saveRoster(rosterPayload.getVersion()); + } + } + + void saveRoster(final String version) { + Collection<XMPPRosterItem> items = xmppRoster_.getItems(); + RosterPayload roster = new RosterPayload(); + roster.setVersion(version); + for (final XMPPRosterItem item : items) { + roster.addItem(new RosterItemPayload(item.getJID(), item.getName(), item.getSubscription(), item.getGroups())); + } + rosterStorage_.setRoster(roster); + } + + public void setUseVersioning(boolean b) { + useVersioning = b; + } +} diff --git a/src/com/isode/stroke/roster/XMPPRosterImpl.java b/src/com/isode/stroke/roster/XMPPRosterImpl.java new file mode 100644 index 0000000..61d0bc3 --- /dev/null +++ b/src/com/isode/stroke/roster/XMPPRosterImpl.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.isode.stroke.elements.RosterItemPayload; +import com.isode.stroke.elements.RosterItemPayload.Subscription; +import com.isode.stroke.jid.JID; + +public class XMPPRosterImpl extends XMPPRoster { + + private Map<JID, XMPPRosterItem> entries_ = new HashMap<JID, XMPPRosterItem>(); + + void addContact(final JID jid, final String name, final Collection<String> groups, RosterItemPayload.Subscription subscription) { + JID bareJID = jid.toBare(); + XMPPRosterItem item = entries_.get(bareJID); + + if (item != null) { + String oldName = item.getName(); + Collection<String> oldGroups = item.getGroups(); + entries_.put(bareJID, new XMPPRosterItem(jid, name, groups, subscription)); + onJIDUpdated.emit(bareJID, oldName, oldGroups); + } + else { + entries_.put(bareJID, new XMPPRosterItem(jid, name, groups, subscription)); + onJIDAdded.emit(bareJID); + } + } + + void removeContact(final JID jid) { + entries_.remove(jid.toBare()); + onJIDRemoved.emit(jid); + } + + void clear() { + entries_.clear(); + onRosterCleared.emit(); + } + + @Override + public boolean containsJID(JID jid) { + return entries_.containsKey(jid); + } + + @Override + public Subscription getSubscriptionStateForJID(JID jid) { + XMPPRosterItem item = entries_.get(jid.toBare()); + if (item != null) return item.getSubscription(); + return RosterItemPayload.Subscription.None; + } + + @Override + public String getNameForJID(JID jid) { + XMPPRosterItem item = entries_.get(jid.toBare()); + if (item != null) return item.getName(); + return ""; + } + + @Override + public Collection<String> getGroupsForJID(JID jid) { + XMPPRosterItem item = entries_.get(jid.toBare()); + if (item != null) return item.getGroups(); + return new ArrayList<String>(); + } + + @Override + public Collection<XMPPRosterItem> getItems() { + return entries_.values(); + } + + @Override + public XMPPRosterItem getItem(JID jid) { + XMPPRosterItem item = entries_.get(jid.toBare()); + if (item != null) return item; + + return null; + } + + @Override + public Set<String> getGroups() { + Set<String> groups = new HashSet<String>(); + for (XMPPRosterItem item : entries_.values()) + groups.addAll(item.getGroups()); + return groups; + } + +} diff --git a/src/com/isode/stroke/roster/XMPPRosterItem.java b/src/com/isode/stroke/roster/XMPPRosterItem.java new file mode 100644 index 0000000..1412f83 --- /dev/null +++ b/src/com/isode/stroke/roster/XMPPRosterItem.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.roster; + +import java.util.Collection; + +import com.isode.stroke.elements.RosterItemPayload; +import com.isode.stroke.jid.JID; + +public class XMPPRosterItem { + private JID jid; + private String name; + private Collection<String> groups; + private RosterItemPayload.Subscription subscription; + + public XMPPRosterItem(final JID jid, final String name, final Collection<String> groups, RosterItemPayload.Subscription subscription) { + this.jid = jid; + this.name = name; + this.groups = groups; + this.subscription = subscription; + } + + public final JID getJID() { + return jid; + } + + public final String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public final Collection<String> getGroups() { + return groups; + } + + public void setGroups(final Collection<String> groups) { + this.groups = groups; + } + + public RosterItemPayload.Subscription getSubscription() { + return subscription; + } +} diff --git a/src/com/isode/stroke/serializer/PresenceSerializer.java b/src/com/isode/stroke/serializer/PresenceSerializer.java index f5a2556..6611c8c 100644 --- a/src/com/isode/stroke/serializer/PresenceSerializer.java +++ b/src/com/isode/stroke/serializer/PresenceSerializer.java @@ -1,16 +1,11 @@ /* - * Copyright (c) 2010, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.serializer; import com.isode.stroke.elements.Presence; -import com.isode.stroke.elements.Stanza; import com.isode.stroke.serializer.xml.XMLElement; public class PresenceSerializer extends GenericStanzaSerializer<Presence> { diff --git a/src/com/isode/stroke/serializer/payloadserializers/CapsInfoSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/CapsInfoSerializer.java index b98a741..cfe1c25 100644 --- a/src/com/isode/stroke/serializer/payloadserializers/CapsInfoSerializer.java +++ b/src/com/isode/stroke/serializer/payloadserializers/CapsInfoSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Isode Limited. + * Copyright (c) 2010-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -34,4 +34,4 @@ public class CapsInfoSerializer extends GenericPayloadSerializer<CapsInfo> { capsElement.setAttribute("ver", capsInfo.getVersion()); return capsElement.serialize(); } -}
\ No newline at end of file +} diff --git a/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java b/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java index 0b62e0f..f4caa29 100644 --- a/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java +++ b/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010-2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.serializer.payloadserializers; @@ -34,8 +30,8 @@ public class FullPayloadSerializerCollection extends PayloadSerializerCollection addSerializer(new MUCOwnerPayloadSerializer(this)); addSerializer(new MUCUserPayloadSerializer(this)); addSerializer(new SoftwareVersionSerializer()); - //addSerializer(new StatusSerializer()); - //addSerializer(new StatusShowSerializer()); + addSerializer(new StatusSerializer()); + addSerializer(new StatusShowSerializer()); addSerializer(new DiscoInfoSerializer()); addSerializer(new DiscoItemsSerializer()); addSerializer(new CapsInfoSerializer()); @@ -45,17 +41,19 @@ public class FullPayloadSerializerCollection extends PayloadSerializerCollection //addSerializer(new SecurityLabelsCatalogSerializer()); //addSerializer(new StreamInitiationSerializer()); //addSerializer(new BytestreamsSerializer()); - //addSerializer(new VCardSerializer()); + addSerializer(new VCardSerializer()); //addSerializer(new VCardUpdateSerializer()); addSerializer(new RawXMLPayloadSerializer()); - //addSerializer(new StorageSerializer()); + addSerializer(new StorageSerializer()); addSerializer(new DelaySerializer()); addSerializer(new FormSerializer()); - //addSerializer(new PrivateStorageSerializer(this)); + addSerializer(new PrivateStorageSerializer(this)); addSerializer(new CommandSerializer()); //addSerializer(new NicknameSerializer()); addSerializer(new SearchPayloadSerializer()); + addSerializer(new ReplaceSerializer()); addSerializer(new LastSerializer()); + addSerializer(new IdleSerializer()); addSerializer(new PubSubSerializer(this)); addSerializer(new PubSubEventSerializer(this)); diff --git a/src/com/isode/stroke/serializer/payloadserializers/IdleSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/IdleSerializer.java new file mode 100644 index 0000000..6d1ae93 --- /dev/null +++ b/src/com/isode/stroke/serializer/payloadserializers/IdleSerializer.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015 Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.serializer.payloadserializers; + +import com.isode.stroke.base.DateTime; +import com.isode.stroke.elements.Idle; +import com.isode.stroke.serializer.GenericPayloadSerializer; + +public class IdleSerializer extends GenericPayloadSerializer<Idle> { + + public IdleSerializer() { + super(Idle.class); + } + + @Override + protected String serializePayload(Idle idle) { + return "<idle xmlns='urn:xmpp:idle:1' since='" + DateTime.dateToString(idle.getSince()) + "'/>"; + } +} diff --git a/src/com/isode/stroke/serializer/payloadserializers/MUCInvitationPayloadSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/MUCInvitationPayloadSerializer.java index e149aa7..3281b0e 100644 --- a/src/com/isode/stroke/serializer/payloadserializers/MUCInvitationPayloadSerializer.java +++ b/src/com/isode/stroke/serializer/payloadserializers/MUCInvitationPayloadSerializer.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2011, Kevin Smith + * Copyright (c) 2011-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.serializer.payloadserializers; @@ -41,6 +37,9 @@ public class MUCInvitationPayloadSerializer extends GenericPayloadSerializer<MUC if (payload.getThread() != null && !payload.getThread().isEmpty()) { mucElement.setAttribute("thread", payload.getThread()); } + if (payload.getIsImpromptu()) { + mucElement.addNode(new XMLElement("impromptu", "http://swift.im/impromptu")); + } return mucElement.serialize(); } } diff --git a/src/com/isode/stroke/serializer/payloadserializers/PrivateStorageSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/PrivateStorageSerializer.java new file mode 100644 index 0000000..c863f71 --- /dev/null +++ b/src/com/isode/stroke/serializer/payloadserializers/PrivateStorageSerializer.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ + +package com.isode.stroke.serializer.payloadserializers; + +import com.isode.stroke.elements.Payload; +import com.isode.stroke.elements.PrivateStorage; +import com.isode.stroke.serializer.GenericPayloadSerializer; +import com.isode.stroke.serializer.PayloadSerializer; +import com.isode.stroke.serializer.PayloadSerializerCollection; +import com.isode.stroke.serializer.xml.XMLElement; +import com.isode.stroke.serializer.xml.XMLRawTextNode; + +class PrivateStorageSerializer extends GenericPayloadSerializer<PrivateStorage>{ + + private final PayloadSerializerCollection serializers; + + public PrivateStorageSerializer(PayloadSerializerCollection serializers) { + super(PrivateStorage.class); + this.serializers = serializers; + } + + + @Override + protected String serializePayload(PrivateStorage storage) { + XMLElement storageElement = new XMLElement("query", "jabber:iq:private"); + Payload payload = storage.getPayload(); + if (payload != null) { + PayloadSerializer serializer = serializers.getPayloadSerializer(payload); + if (serializer != null) { + storageElement.addNode(new XMLRawTextNode(serializer.serialize(payload))); + } + } + return storageElement.serialize(); + } + +} diff --git a/src/com/isode/stroke/serializer/payloadserializers/ReplaceSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/ReplaceSerializer.java new file mode 100644 index 0000000..b6b49e2 --- /dev/null +++ b/src/com/isode/stroke/serializer/payloadserializers/ReplaceSerializer.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012-2015, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +package com.isode.stroke.serializer.payloadserializers; + +import com.isode.stroke.elements.Replace; +import com.isode.stroke.serializer.GenericPayloadSerializer; + +public class ReplaceSerializer extends GenericPayloadSerializer<Replace> { + + public ReplaceSerializer() { + super(Replace.class); + } + + protected String serializePayload(Replace replace) { + return "<replace id = '" + replace.getID() + "' xmlns='urn:xmpp:message-correct:0'/>"; + } + +} diff --git a/src/com/isode/stroke/serializer/payloadserializers/StatusSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/StatusSerializer.java new file mode 100644 index 0000000..6a9bb23 --- /dev/null +++ b/src/com/isode/stroke/serializer/payloadserializers/StatusSerializer.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.serializer.payloadserializers; + +import com.isode.stroke.elements.Status; +import com.isode.stroke.serializer.GenericPayloadSerializer; +import com.isode.stroke.serializer.xml.XMLElement; +import com.isode.stroke.serializer.xml.XMLTextNode; + +public class StatusSerializer extends GenericPayloadSerializer<Status> { + + public StatusSerializer() { + super(Status.class); + } + + protected String serializePayload(Status status) { + XMLElement element = new XMLElement("status"); + element.addNode(new XMLTextNode(status.getText())); + return element.serialize(); + } +} diff --git a/src/com/isode/stroke/serializer/payloadserializers/StatusShowSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/StatusShowSerializer.java new file mode 100644 index 0000000..e7cb587 --- /dev/null +++ b/src/com/isode/stroke/serializer/payloadserializers/StatusShowSerializer.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.serializer.payloadserializers; + +import com.isode.stroke.elements.StatusShow; +import com.isode.stroke.serializer.GenericPayloadSerializer; + +public class StatusShowSerializer extends GenericPayloadSerializer<StatusShow> { + + public StatusShowSerializer() { + super(StatusShow.class); + } + + protected String serializePayload(StatusShow statusShow) { + if (statusShow.getType () == StatusShow.Type.Online || statusShow.getType() == StatusShow.Type.None) { + return ""; + } + else { + String result = "<show>"; + switch (statusShow.getType()) { + case Away: result += "away"; break; + case XA: result += "xa"; break; + case FFC: result += "chat"; break; + case DND: result += "dnd"; break; + case Online: assert(false); break; + case None: assert(false); break; + } + result += "</show>"; + return result; + } + } + +} diff --git a/src/com/isode/stroke/serializer/payloadserializers/StorageSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/StorageSerializer.java new file mode 100644 index 0000000..2a072ad --- /dev/null +++ b/src/com/isode/stroke/serializer/payloadserializers/StorageSerializer.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ + +package com.isode.stroke.serializer.payloadserializers; + +import com.isode.stroke.elements.Storage; +import com.isode.stroke.serializer.GenericPayloadSerializer; +import com.isode.stroke.serializer.xml.XMLElement; +import com.isode.stroke.serializer.xml.XMLTextNode; + +class StorageSerializer extends GenericPayloadSerializer<Storage>{ + + public StorageSerializer() { + super(Storage.class); + } + + @Override + protected String serializePayload(Storage storage) { + XMLElement storageElement = new XMLElement("storage", "storage:bookmarks"); + + for (final Storage.Room room : storage.getRooms()) { + XMLElement conferenceElement = new XMLElement("conference"); + conferenceElement.setAttribute("name", room.name); + conferenceElement.setAttribute("jid", room.jid.toString()); + conferenceElement.setAttribute("autojoin", room.autoJoin ? "1" : "0"); + if (room.nick != null && !room.nick.isEmpty()) { + XMLElement nickElement = new XMLElement("nick"); + nickElement.addNode(new XMLTextNode(room.nick)); + conferenceElement.addNode(nickElement); + } + if (room.password != null) { + XMLElement passwordElement = new XMLElement("password"); + passwordElement.addNode(new XMLTextNode(room.password)); + conferenceElement.addNode(passwordElement); + } + storageElement.addNode(conferenceElement); + } + + for (final Storage.URL url : storage.getURLs()) { + XMLElement urlElement = new XMLElement("url"); + urlElement.setAttribute("name", url.name); + urlElement.setAttribute("url", url.url); + storageElement.addNode(urlElement); + } + + return storageElement.serialize(); + } + +} diff --git a/src/com/isode/stroke/serializer/payloadserializers/VCardSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/VCardSerializer.java new file mode 100644 index 0000000..8a98f46 --- /dev/null +++ b/src/com/isode/stroke/serializer/payloadserializers/VCardSerializer.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ + +package com.isode.stroke.serializer.payloadserializers; + +import com.isode.stroke.base.DateTime; +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.serializer.GenericPayloadSerializer; +import com.isode.stroke.serializer.xml.XMLElement; +import com.isode.stroke.serializer.xml.XMLRawTextNode; +import com.isode.stroke.stringcodecs.Base64; + +class VCardSerializer extends GenericPayloadSerializer<VCard>{ + + public VCardSerializer() { + super(VCard.class); + } + + @Override + protected String serializePayload(VCard vcard) { + XMLElement queryElement = new XMLElement("vCard", "vcard-temp"); + if (vcard.getVersion() != null && !vcard.getVersion().isEmpty()) { + queryElement.addNode(new XMLElement("VERSION", "", vcard.getVersion())); + } + if (vcard.getFullName() != null && !vcard.getFullName().isEmpty()) { + queryElement.addNode(new XMLElement("FN", "", vcard.getFullName())); + } + if (vcard.getGivenName() != null && !vcard.getGivenName().isEmpty() + || vcard.getFamilyName() != null && !vcard.getFamilyName().isEmpty() + || vcard.getMiddleName() != null && !vcard.getMiddleName().isEmpty() + || vcard.getPrefix() != null && !vcard.getPrefix().isEmpty() + || vcard.getSuffix() != null && !vcard.getSuffix().isEmpty()) { + XMLElement nameElement = new XMLElement("N"); + if (vcard.getFamilyName() != null && !vcard.getFamilyName().isEmpty()) { + nameElement.addNode(new XMLElement("FAMILY", "", vcard.getFamilyName())); + } + if (vcard.getGivenName() != null && !vcard.getGivenName().isEmpty()) { + nameElement.addNode(new XMLElement("GIVEN", "", vcard.getGivenName())); + } + if (vcard.getMiddleName() != null && !vcard.getMiddleName().isEmpty()) { + nameElement.addNode(new XMLElement("MIDDLE", "", vcard.getMiddleName())); + } + if (vcard.getPrefix() != null && !vcard.getPrefix().isEmpty()) { + nameElement.addNode(new XMLElement("PREFIX", "", vcard.getPrefix())); + } + if (vcard.getSuffix() != null && !vcard.getSuffix().isEmpty()) { + nameElement.addNode(new XMLElement("SUFFIX", "", vcard.getSuffix())); + } + queryElement.addNode(nameElement); + } + if (vcard.getEMailAddresses() != null) for (final VCard.EMailAddress emailAddress : vcard.getEMailAddresses()) { + XMLElement emailElement = new XMLElement("EMAIL"); + emailElement.addNode(new XMLElement("USERID", "", emailAddress.address)); + if (emailAddress.isHome) { + emailElement.addNode(new XMLElement("HOME")); + } + if (emailAddress.isWork) { + emailElement.addNode(new XMLElement("WORK")); + } + if (emailAddress.isInternet) { + emailElement.addNode(new XMLElement("INTERNET")); + } + if (emailAddress.isPreferred) { + emailElement.addNode(new XMLElement("PREF")); + } + if (emailAddress.isX400) { + emailElement.addNode(new XMLElement("X400")); + } + queryElement.addNode(emailElement); + } + if (vcard.getNickname() != null && !vcard.getNickname().isEmpty()) { + queryElement.addNode(new XMLElement("NICKNAME", "", vcard.getNickname())); + } + if (vcard.getPhoto() != null && !vcard.getPhoto().isEmpty() || vcard.getPhotoType() != null && !vcard.getPhotoType().isEmpty()) { + XMLElement photoElement = new XMLElement("PHOTO"); + if (vcard.getPhotoType() != null && !vcard.getPhotoType().isEmpty()) { + photoElement.addNode(new XMLElement("TYPE", "", vcard.getPhotoType())); + } + if (vcard.getPhoto() != null && !vcard.getPhoto().isEmpty()) { + photoElement.addNode(new XMLElement("BINVAL", "", Base64.encode(vcard.getPhoto()))); + } + queryElement.addNode(photoElement); + } + if (vcard.getBirthday() != null) { + queryElement.addNode(new XMLElement("BDAY", "", DateTime.dateToString(vcard.getBirthday()))); + } + + if (vcard.getTelephones() != null) for (final VCard.Telephone telephone : vcard.getTelephones()) { + XMLElement telElement = new XMLElement("TEL"); + telElement.addNode(new XMLElement("NUMBER", "", telephone.number)); + if (telephone.isHome) { + telElement.addNode(new XMLElement("HOME")); + } + if (telephone.isWork) { + telElement.addNode(new XMLElement("WORK")); + } + if (telephone.isVoice) { + telElement.addNode(new XMLElement("VOICE")); + } + if (telephone.isFax) { + telElement.addNode(new XMLElement("FAX")); + } + if (telephone.isPager) { + telElement.addNode(new XMLElement("PAGER")); + } + if (telephone.isMSG) { + telElement.addNode(new XMLElement("MSG")); + } + if (telephone.isCell) { + telElement.addNode(new XMLElement("CELL")); + } + if (telephone.isVideo) { + telElement.addNode(new XMLElement("VIDEO")); + } + if (telephone.isBBS) { + telElement.addNode(new XMLElement("BBS")); + } + if (telephone.isModem) { + telElement.addNode(new XMLElement("MODEM")); + } + if (telephone.isISDN) { + telElement.addNode(new XMLElement("ISDN")); + } + if (telephone.isPCS) { + telElement.addNode(new XMLElement("PCS")); + } + if (telephone.isPreferred) { + telElement.addNode(new XMLElement("PREF")); + } + queryElement.addNode(telElement); + } + + if (vcard.getAddresses() != null) for (final VCard.Address address : vcard.getAddresses()) { + XMLElement adrElement = new XMLElement("ADR"); + if (!address.poBox.isEmpty()) { + adrElement.addNode(new XMLElement("POBOX", "", address.poBox)); + } + if (!address.addressExtension.isEmpty()) { + adrElement.addNode(new XMLElement("EXTADD", "", address.addressExtension)); + } + if (!address.street.isEmpty()) { + adrElement.addNode(new XMLElement("STREET", "", address.street)); + } + if (!address.locality.isEmpty()) { + adrElement.addNode(new XMLElement("LOCALITY", "", address.locality)); + } + if (!address.region.isEmpty()) { + adrElement.addNode(new XMLElement("REGION", "", address.region)); + } + if (!address.postalCode.isEmpty()) { + adrElement.addNode(new XMLElement("PCODE", "", address.postalCode)); + } + if (!address.country.isEmpty()) { + adrElement.addNode(new XMLElement("CTRY", "", address.country)); + } + + if (address.isHome) { + adrElement.addNode(new XMLElement("HOME")); + } + if (address.isWork) { + adrElement.addNode(new XMLElement("WORK")); + } + if (address.isPostal) { + adrElement.addNode(new XMLElement("POSTAL")); + } + if (address.isParcel) { + adrElement.addNode(new XMLElement("PARCEL")); + } + if (address.deliveryType == VCard.DeliveryType.DomesticDelivery) { + adrElement.addNode(new XMLElement("DOM")); + } + if (address.deliveryType == VCard.DeliveryType.InternationalDelivery) { + adrElement.addNode(new XMLElement("INTL")); + } + if (address.isPreferred) { + adrElement.addNode(new XMLElement("PREF")); + } + queryElement.addNode(adrElement); + } + + if (vcard.getAddressLabels() != null) for (final VCard.AddressLabel addressLabel : vcard.getAddressLabels()) { + XMLElement labelElement = new XMLElement("LABEL"); + + for (final String line : addressLabel.lines) { + labelElement.addNode(new XMLElement("LINE", "", line)); + } + + if (addressLabel.isHome) { + labelElement.addNode(new XMLElement("HOME")); + } + if (addressLabel.isWork) { + labelElement.addNode(new XMLElement("WORK")); + } + if (addressLabel.isPostal) { + labelElement.addNode(new XMLElement("POSTAL")); + } + if (addressLabel.isParcel) { + labelElement.addNode(new XMLElement("PARCEL")); + } + if (addressLabel.deliveryType == VCard.DeliveryType.DomesticDelivery) { + labelElement.addNode(new XMLElement("DOM")); + } + if (addressLabel.deliveryType == VCard.DeliveryType.InternationalDelivery) { + labelElement.addNode(new XMLElement("INTL")); + } + if (addressLabel.isPreferred) { + labelElement.addNode(new XMLElement("PREF")); + } + queryElement.addNode(labelElement); + } + + if (vcard.getJIDs() != null) for (final JID jid : vcard.getJIDs()) { + queryElement.addNode(new XMLElement("JID", "", jid.toString())); + } + + if (vcard.getDescription() != null && !vcard.getDescription().isEmpty()) { + queryElement.addNode(new XMLElement("DESC", "", vcard.getDescription())); + } + + if (vcard.getOrganizations() != null) for (final VCard.Organization org : vcard.getOrganizations()) { + XMLElement orgElement = new XMLElement("ORG"); + if (!org.name.isEmpty()) { + orgElement.addNode(new XMLElement("ORGNAME", "", org.name)); + } + if (!org.units.isEmpty()) { + for (final String unit : org.units) { + orgElement.addNode(new XMLElement("ORGUNIT", "", unit)); + } + } + queryElement.addNode(orgElement); + } + + if (vcard.getTitles() != null) for (final String title : vcard.getTitles()) { + queryElement.addNode(new XMLElement("TITLE", "", title)); + } + + if (vcard.getRoles() != null) for (final String role : vcard.getRoles()) { + queryElement.addNode(new XMLElement("ROLE", "", role)); + } + + if (vcard.getURLs() != null) for (final String url : vcard.getURLs()) { + queryElement.addNode(new XMLElement("URL", "", url)); + } + + if (vcard.getUnknownContent() != null && !vcard.getUnknownContent().isEmpty()) { + queryElement.addNode(new XMLRawTextNode(vcard.getUnknownContent())); + } + return queryElement.serialize(); + } + +} diff --git a/src/com/isode/stroke/serializer/xml/XMLTextNode.java b/src/com/isode/stroke/serializer/xml/XMLTextNode.java index fa1881e..da1b03f 100644 --- a/src/com/isode/stroke/serializer/xml/XMLTextNode.java +++ b/src/com/isode/stroke/serializer/xml/XMLTextNode.java @@ -1,9 +1,5 @@ /* - * Copyright (c) 2010-2012, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.serializer.xml; @@ -13,7 +9,7 @@ public class XMLTextNode implements XMLNode { private String text_; public XMLTextNode(String text) { - text_ = text; + text_ = text != null ? text : ""; text_ = text_.replaceAll("&", "&"); // Should come first text_ = text_.replaceAll("<", "<"); text_ = text_.replaceAll(">", ">"); diff --git a/src/com/isode/stroke/signals/Signal7.java b/src/com/isode/stroke/signals/Signal7.java new file mode 100644 index 0000000..296cc02 --- /dev/null +++ b/src/com/isode/stroke/signals/Signal7.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2012-2015, Isode Limited, London, England. + * All rights reserved. + */ + +package com.isode.stroke.signals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot; + + +/** + * An approximation of the boost::signals system with 4 parameters + * @param <T1> Type 1 + * @param <T2> Type 2 + * @param <T3> Type 3 + * @param <T4> Type 4 + */ +public class Signal7<T1, T2, T3, T4, T5, T6, T7> { + private final Map<SignalConnection, Slot7<T1, T2, T3, T4, T5, T6, T7> > binds_ = Collections.synchronizedMap( + new HashMap<SignalConnection, Slot7<T1, T2, T3, T4, T5, T6, T7> >()); + + /** + * Add a slot which will be notified + * @param bind slot, not null + * @return signal connection + */ + public SignalConnection connect(Slot7<T1, T2, T3, T4, T5, T6, T7> bind) { + final SignalConnection connection = new SignalConnection(); + binds_.put(connection, bind); + connection.onDestroyed.connect(new Slot() { + public void call() { + binds_.remove(connection); + } + }); + return connection; + } + + /** + * Notify all slots(listeners) + * @param p1 parameter value 1 + * @param p2 parameter value 2 + * @param p3 parameter value 3 + * @param p4 parameter value 4 + */ + public void emit(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7) { + List<Slot7<T1, T2, T3, T4, T5, T6, T7>> binds = new ArrayList<Slot7<T1, T2, T3, T4, T5, T6, T7>>(); + binds.addAll(binds_.values()); + for (Slot7<T1, T2, T3, T4, T5, T6, T7> bind : binds) { + bind.call(p1, p2, p3, p4, p5, p6, p7); + } + } + + /** + * Remove all slots(listeners) + */ + public void disconnectAll() { + binds_.clear(); + } +} diff --git a/src/com/isode/stroke/signals/Slot7.java b/src/com/isode/stroke/signals/Slot7.java new file mode 100644 index 0000000..7df6973 --- /dev/null +++ b/src/com/isode/stroke/signals/Slot7.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012-2015, Isode Limited, London, England. + * All rights reserved. + */ + +package com.isode.stroke.signals; + +/** + * Bind class for connecting to a signal with 7 parameters. + * @param <T1> Type 1 + * @param <T2> Type 2 + * @param <T3> Type 3 + * @param <T4> Type 4 + * @param <T5> Type 5 + * @param <T6> Type 6 + * @param <T7> Type 7 + */ +public interface Slot7<T1, T2, T3, T4, T5, T6, T7> { + /** + * This method will be called on notification from a signal + * @param p1 parameter value 1 + * @param p2 parameter value 2 + * @param p3 parameter value 3 + * @param p4 parameter value 4 + * @param p5 parameter value 5 + * @param p6 parameter value 6 + * @param p7 parameter value 7 + */ + void call(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7); +} diff --git a/src/com/isode/stroke/stringcodecs/Base64.java b/src/com/isode/stroke/stringcodecs/Base64.java index 9837993..e19bb5d 100644 --- a/src/com/isode/stroke/stringcodecs/Base64.java +++ b/src/com/isode/stroke/stringcodecs/Base64.java @@ -1,10 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ -/* - * Copyright (c) 2010, Isode Limited, London, England. + * Copyright (c) 2010-2015, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.stringcodecs; @@ -20,4 +15,8 @@ public class Base64 { public static String encode(ByteArray input) { return Base64BSD.encodeToString(input.getData(), false); } + + public static String encode(byte[] input) { + return Base64BSD.encodeToString(input, false); + } } diff --git a/src/com/isode/stroke/vcards/GetVCardRequest.java b/src/com/isode/stroke/vcards/GetVCardRequest.java new file mode 100644 index 0000000..e563106 --- /dev/null +++ b/src/com/isode/stroke/vcards/GetVCardRequest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.vcards; + +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.GenericRequest; +import com.isode.stroke.queries.IQRouter; + +public class GetVCardRequest extends GenericRequest<VCard> { + public static GetVCardRequest create(final JID jid, IQRouter router) { + return new GetVCardRequest(jid, router); + } + + private GetVCardRequest(final JID jid, IQRouter router) { + super(IQ.Type.Get, jid, new VCard(), router); + } + +} diff --git a/src/com/isode/stroke/vcards/SetVCardRequest.java b/src/com/isode/stroke/vcards/SetVCardRequest.java new file mode 100644 index 0000000..85b908f --- /dev/null +++ b/src/com/isode/stroke/vcards/SetVCardRequest.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.vcards; + +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.GenericRequest; +import com.isode.stroke.queries.IQRouter; + +public class SetVCardRequest extends GenericRequest<VCard> { + + public static SetVCardRequest create(VCard vcard, IQRouter router) { + return new SetVCardRequest(vcard, router); + } + + private SetVCardRequest(VCard vcard, IQRouter router) { + super(IQ.Type.Set, new JID(), vcard, router); + } + +} diff --git a/src/com/isode/stroke/vcards/VCardManager.java b/src/com/isode/stroke/vcards/VCardManager.java new file mode 100644 index 0000000..593d244 --- /dev/null +++ b/src/com/isode/stroke/vcards/VCardManager.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.vcards; + +import java.util.HashSet; +import java.util.Set; + +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.Signal2; +import com.isode.stroke.signals.Slot2; + +public class VCardManager { + private final JID ownJID; + private final IQRouter iqRouter; + private final VCardStorage storage; + private final Set<JID> requestedVCards = new HashSet<JID>(); + + /** + * The JID will always be bare. + */ + public final Signal2<JID, VCard> onVCardChanged = new Signal2<JID, VCard>(); + + /** + * Emitted when our own vcard changes. + * + * onVCardChanged will also be emitted. + */ + public final Signal1<VCard> onOwnVCardChanged = new Signal1<VCard>(); + + public VCardManager(final JID ownJID, IQRouter iqRouter, VCardStorage vcardStorage) { + this.ownJID = ownJID; + this.iqRouter = iqRouter; + this.storage = vcardStorage; + } + + void delete() { + } + + public VCard getVCard(final JID jid) { + return storage.getVCard(jid); + } + + public VCard getVCardAndRequestWhenNeeded(final JID jid) { + VCard vcard = storage.getVCard(jid); + if (vcard == null) { + requestVCard(jid); + } + return vcard; + } + + void requestVCard(final JID requestedJID) { + final JID jid = requestedJID.compare(ownJID, JID.CompareType.WithoutResource) == 0 ? new JID() : requestedJID; + if (requestedVCards.contains(jid)) { + return; + } + GetVCardRequest request = GetVCardRequest.create(jid, iqRouter); + request.onResponse.connect(new Slot2<VCard, ErrorPayload>() { + @Override + public void call(VCard p1, ErrorPayload p2) { + handleVCardReceived(jid, p1, p2); + } + }); + request.send(); + requestedVCards.add(jid); + } + + public void requestOwnVCard() { + requestVCard(new JID()); + } + + + void handleVCardReceived(final JID actualJID, VCard vcard, ErrorPayload error) { + if (error != null || vcard == null) { + vcard = new VCard(); + } + requestedVCards.remove(actualJID); + JID jid = actualJID.isValid() ? actualJID : ownJID.toBare(); + setVCard(jid, vcard); + } + + SetVCardRequest createSetVCardRequest(final VCard vcard) { + SetVCardRequest request = SetVCardRequest.create(vcard, iqRouter); + request.onResponse.connect(new Slot2<VCard, ErrorPayload>() { + @Override + public void call(VCard p1, ErrorPayload p2) { + handleSetVCardResponse(vcard, p2); + } + }); + return request; + } + + void handleSetVCardResponse(VCard vcard, ErrorPayload error) { + if (error == null) { + setVCard(ownJID.toBare(), vcard); + } + } + + void setVCard(final JID jid, VCard vcard) { + storage.setVCard(jid, vcard); + onVCardChanged.emit(jid, vcard); + if (jid.compare(ownJID, JID.CompareType.WithoutResource) == 0) { + onOwnVCardChanged.emit(vcard); + } + } + +// String getPhotoHash(final JID jid) { +// return storage.getPhotoHash(jid); +// } +} diff --git a/src/com/isode/stroke/vcards/VCardMemoryStorage.java b/src/com/isode/stroke/vcards/VCardMemoryStorage.java new file mode 100644 index 0000000..d666d98 --- /dev/null +++ b/src/com/isode/stroke/vcards/VCardMemoryStorage.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.vcards; + +import java.util.HashMap; +import java.util.Map; + +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; + +public class VCardMemoryStorage extends VCardStorage { + public VCardMemoryStorage(CryptoProvider crypto) { + super(crypto); + } + + private Map<JID, VCard> vcards = new HashMap<JID, VCard>(); + + @Override + public VCard getVCard(JID jid) { + return vcards.get(jid); + } + + @Override + public void setVCard(JID jid, VCard vcard) { + vcards.put(jid, vcard); + } + +} diff --git a/src/com/isode/stroke/vcards/VCardStorage.java b/src/com/isode/stroke/vcards/VCardStorage.java new file mode 100644 index 0000000..9021599 --- /dev/null +++ b/src/com/isode/stroke/vcards/VCardStorage.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010-2015, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.vcards; + +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.elements.VCard; +import com.isode.stroke.jid.JID; +import com.isode.stroke.stringcodecs.Hexify; + +public abstract class VCardStorage { + private CryptoProvider crypto; + + public abstract VCard getVCard(JID jid); + public abstract void setVCard(JID jid, VCard vcard); + + public VCardStorage(CryptoProvider crypto) { + this.crypto = crypto; + } + + public void delete() {}; + + public String getPhotoHash(final JID jid) { + VCard vCard = getVCard(jid); + if (vCard != null && vCard.getPhoto().getSize() != 0) { + return Hexify.hexify(crypto.getSHA1Hash(vCard.getPhoto())); + } + else { + return ""; + } + } +} |