Start implementing first MIX discovery methods

This commit is contained in:
Paul Schaub 2020-04-04 22:47:47 +02:00
parent e6a1cb2f87
commit da7d2e7e08
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
10 changed files with 307 additions and 2 deletions

View File

@ -2,4 +2,8 @@ package org.jivesoftware.smackx.mix.core;
public class MixChannel {
public MixChannel(/*EntityBareJid mixChannelAddress, DiscoverInfo channelInfo*/) {
//this.identityName = channelInfo.getIdentities("conference", "mix").get(0).getName();
}
}

View File

@ -16,18 +16,39 @@
*/
package org.jivesoftware.smackx.mix.core;
import static org.jivesoftware.smackx.mix.core.MixCoreConstants.FEATURE_CORE_1;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mix.core.exception.NotAMixChannelOrNoPermissionToSubscribeException;
import org.jivesoftware.smackx.mix.core.exception.NotAMixServiceException;
import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.util.cache.ExpirationCache;
public final class MixManager extends Manager {
private static final Map<XMPPConnection, MixManager> INSTANCES = new WeakHashMap<>();
private static final ExpirationCache<DomainBareJid, MixService> KNOWN_MIX_SERVICES =
new ExpirationCache<>(100, 1000 * 60 * 60 * 24);
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@ -38,6 +59,8 @@ public final class MixManager extends Manager {
});
}
private final ServiceDiscoveryManager serviceDiscoveryManager;
public static MixManager getInstanceFor(XMPPConnection connection) {
MixManager manager = INSTANCES.get(connection);
if (manager == null) {
@ -49,6 +72,107 @@ public final class MixManager extends Manager {
private MixManager(XMPPConnection connection) {
super(connection);
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(MixCoreConstants.FEATURE_CORE_1);
serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection());
serviceDiscoveryManager.addFeature(FEATURE_CORE_1);
}
public List<EntityBareJid> discoverMixChannels(DomainBareJid mixServiceAddress)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, NotAMixServiceException,
SmackException.FeatureNotSupportedException {
MixService mixService = getOrDiscoverMixService(mixServiceAddress);
if (!mixService.isSearchable()) {
throw new SmackException.FeatureNotSupportedException(MixCoreConstants.FEATURE_SEARCHABLE_1, mixServiceAddress);
}
DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(mixServiceAddress);
List<EntityBareJid> channelJids = discoverItems.getItems().stream()
.map(DiscoverItems.Item::getEntityID)
.map(Jid::asEntityBareJidOrThrow)
.collect(Collectors.toList());
return channelJids;
}
private MixService getOrDiscoverMixService(DomainBareJid mixServiceAddress)
throws SmackException.NoResponseException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException, NotAMixServiceException {
if (KNOWN_MIX_SERVICES.containsKey(mixServiceAddress)) {
return KNOWN_MIX_SERVICES.get(mixServiceAddress);
}
return discoverMixService(mixServiceAddress);
}
private MixService discoverMixService(DomainBareJid mixServiceAddress)
throws SmackException.NoResponseException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException, NotAMixServiceException {
DiscoverInfo mixServiceDiscoverInfo = serviceDiscoveryManager.discoverInfo(mixServiceAddress);
if (!mixServiceDiscoverInfo.hasIdentity("conference", "mix")) {
throw new NotAMixServiceException("Identity of the service MUST have a category 'conference' and a type of 'mix'.");
}
if (!mixServiceDiscoverInfo.containsFeature(FEATURE_CORE_1)) {
throw new NotAMixServiceException("Service MUST include the '" + FEATURE_CORE_1 + "' feature.");
}
mixServiceStandardComplianceChecks(mixServiceDiscoverInfo);
MixService mixService = new MixService(mixServiceAddress, mixServiceDiscoverInfo);
KNOWN_MIX_SERVICES.put(mixServiceAddress, mixService);
return mixService;
}
public MixChannel discoverMixChannelInformation(EntityBareJid mixChannelAddress)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, NotAMixChannelOrNoPermissionToSubscribeException {
DiscoverInfo channelInfo = serviceDiscoveryManager.discoverInfo(mixChannelAddress);
if (!channelInfo.hasIdentity("conference", "mix")) {
throw new NotAMixChannelOrNoPermissionToSubscribeException(
"DiscoverInfo did not contain identity with category 'conference' and type 'mix'.");
}
mixChannelStandardComplianceChecks(channelInfo);
//return new MixChannel(mixChannelAddress, channelInfo);
return null;
}
/**
* Perform checks for standards compliance on the service.
* These checks are not strictly necessary, but may be used to identify faulty server implementations.
*
* @param info DiscoverInfo of the mix service.
*/
private void mixServiceStandardComplianceChecks(DiscoverInfo info) {
if (info.containsFeature(MamElements.NAMESPACE)) {
// XEP-0369: §6.1
throw new AssertionError("A MIX service MUST NOT advertise support for MAM.");
}
if (info.containsFeature(PubSub.NAMESPACE)) {
// XEP-0369: §6.1
throw new AssertionError("A MIX service MUST NOT advertise support for PubSub.");
}
}
private void mixChannelStandardComplianceChecks(DiscoverInfo channelInfo) throws NotAMixChannelOrNoPermissionToSubscribeException {
if (!channelInfo.containsFeature(FEATURE_CORE_1)) {
throw new NotAMixChannelOrNoPermissionToSubscribeException(
"MIX channel did not advertise feature '" + FEATURE_CORE_1 + "'.");
}
if (!channelInfo.containsFeature(MamElements.NAMESPACE)) {
throw new NotAMixChannelOrNoPermissionToSubscribeException(
"MIX channel did not advertise feature '" + MamElements.NAMESPACE + "'.");
}
}
public MixChannel join(EntityBareJid channelAddress, String nick)
throws XMPPException.XMPPErrorException, NotAMixChannelOrNoPermissionToSubscribeException,
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
MixChannel channel = discoverMixChannelInformation(channelAddress);
return null;
}
}

View File

@ -17,10 +17,11 @@
package org.jivesoftware.smackx.mix.core;
import org.jivesoftware.smackx.mix.core.element.SubscribeElement;
import org.jivesoftware.smackx.mix.presence.MixPresenceConstants;
public class MixNodes {
public static final SubscribeElement NODE_MESSAGES = new SubscribeElement(MixCoreConstants.NODE_MESSAGES);
public static final SubscribeElement NODE_PRESENCE = new SubscribeElement(MixCoreConstants.NODE_PRESENCE);
public static final SubscribeElement NODE_PRESENCE = new SubscribeElement(MixPresenceConstants.NODE_PRESENCE);
public static final SubscribeElement NODE_PARTICIPANTS = new SubscribeElement(MixCoreConstants.NODE_PARTICIPANTS);
public static final SubscribeElement NODE_INFO = new SubscribeElement(MixCoreConstants.NODE_INFO);
}

View File

@ -0,0 +1,37 @@
package org.jivesoftware.smackx.mix.core;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jxmpp.jid.DomainBareJid;
public class MixService {
private final DomainBareJid serviceJid;
private boolean isSearchable;
private boolean isSupportingChannelCreation;
public MixService(DomainBareJid serviceJid, DiscoverInfo serviceInfo) {
this.serviceJid = serviceJid;
this.isSearchable = discoverIsSearchable(serviceInfo);
this.isSupportingChannelCreation = discoverIsSupportingChannelCreation(serviceInfo);
}
public boolean isSearchable() {
return isSearchable;
}
public boolean isSupportingChannelCreation() {
return isSupportingChannelCreation;
}
private boolean discoverIsSupportingChannelCreation(DiscoverInfo serviceInfo) {
return serviceInfo.containsFeature(MixCoreConstants.FEATURE_CREATE_CHANNEL_1);
}
private boolean discoverIsSearchable(DiscoverInfo serviceInfo) {
return serviceInfo.containsFeature(MixCoreConstants.FEATURE_SEARCHABLE_1);
}
}

View File

@ -0,0 +1,10 @@
package org.jivesoftware.smackx.mix.core.exception;
public class NotAMixChannelOrNoPermissionToSubscribeException extends Exception {
private static final long serialVersionUID = 1L;
public NotAMixChannelOrNoPermissionToSubscribeException(String message) {
super(message);
}
}

View File

@ -0,0 +1,10 @@
package org.jivesoftware.smackx.mix.core.exception;
public class NotAMixServiceException extends Exception {
private static final long serialVersionUID = 1L;
public NotAMixServiceException(String message) {
super(message);
}
}

View File

@ -0,0 +1,16 @@
package org.jivesoftware.smackx.mix.misc;
import static org.jivesoftware.smackx.mix.core.MixCoreConstants.URN_XMPP_MIX;
public class MixMiscConstants {
public static final String URN_XMPP_MIX_MISC = URN_XMPP_MIX + ":misc";
public static final String URN_XMPP_MIX_MISC_0 = URN_XMPP_MIX_MISC + ":0";
public static final String NAMESPACE_MISC_0 = URN_XMPP_MIX_MISC_0;
public static final String NAMESPACE_MISC = NAMESPACE_MISC_0;
public static final String FEATURE_NICK_REGISTER_0 = NAMESPACE_MISC_0 + "#nick-register";
}

View File

@ -0,0 +1,45 @@
package org.jivesoftware.smackx.mix.misc.element;
import static org.jivesoftware.smackx.mix.misc.MixMiscConstants.NAMESPACE_MISC_0;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.mix.core.element.NickElement;
public abstract class RegisterElement implements ExtensionElement {
public static final String ELEMENT = "register";
private final NickElement nick;
public RegisterElement(NickElement nickElement) {
this.nick = Objects.requireNonNull(nickElement, "Nick element MUST NOT be null.");
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
return new XmlStringBuilder(this)
.rightAngleBracket()
.append(nick)
.closeElement(this);
}
public static class V0 extends RegisterElement {
public V0(NickElement nickElement) {
super(nickElement);
}
@Override
public String getNamespace() {
return NAMESPACE_MISC_0;
}
}
}

View File

@ -0,0 +1,37 @@
package org.jivesoftware.smackx.mix.misc.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.mix.core.element.NickElement;
import org.jivesoftware.smackx.mix.core.element.SetNickElement;
import org.jivesoftware.smackx.mix.misc.element.RegisterElement;
public abstract class RegisterElementProvider<E extends RegisterElement> extends ExtensionElementProvider<E> {
public static class V0 extends RegisterElementProvider<RegisterElement.V0> {
@Override
public RegisterElement.V0 parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
NickElement nickElement = null;
while (true) {
XmlPullParser.TagEvent tagEvent = parser.nextTag();
String name = parser.getName();
if (tagEvent == XmlPullParser.TagEvent.START_ELEMENT) {
if (NickElement.ELEMENT.equals(name)) {
nickElement = new NickElement(parser.nextText());
}
} else if (tagEvent == XmlPullParser.TagEvent.END_ELEMENT) {
if (SetNickElement.ELEMENT.equals(name)) {
return new RegisterElement.V0(nickElement);
}
}
}
}
}
}

View File

@ -0,0 +1,21 @@
package org.jivesoftware.smackx.mix.misc.element;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import org.jivesoftware.smackx.mix.core.element.NickElement;
import org.junit.jupiter.api.Test;
public class RegisterElementTest {
@Test
public void v0testSerialization() {
RegisterElement register = new RegisterElement.V0(new NickElement("thirdwitch"));
String expectedXml = "" +
"<register xmlns='urn:xmpp:mix:misc:0'>\n" +
" <nick>thirdwitch</nick>\n" +
"</register>\n";
assertXmlSimilar(expectedXml, register.toXML());
}
}