VersionManager.java

  1. /**
  2.  *
  3.  * Copyright 2014 Georg Lukas, 2021 Florian Schmaus.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.iqversion;

  18. import java.util.Map;
  19. import java.util.WeakHashMap;

  20. import org.jivesoftware.smack.ConnectionCreationListener;
  21. import org.jivesoftware.smack.Manager;
  22. import org.jivesoftware.smack.Smack;
  23. import org.jivesoftware.smack.SmackException.NoResponseException;
  24. import org.jivesoftware.smack.SmackException.NotConnectedException;
  25. import org.jivesoftware.smack.XMPPConnection;
  26. import org.jivesoftware.smack.XMPPConnectionRegistry;
  27. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  28. import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
  29. import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
  30. import org.jivesoftware.smack.packet.IQ;
  31. import org.jivesoftware.smack.packet.StanzaError.Condition;
  32. import org.jivesoftware.smack.util.StringUtils;

  33. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  34. import org.jivesoftware.smackx.iqversion.packet.Version;

  35. import org.jxmpp.jid.Jid;

  36. /**
  37.  * A Version Manager that automatically responds to version IQs with a predetermined reply.
  38.  *
  39.  * <p>
  40.  * The VersionManager takes care of handling incoming version request IQs, according to
  41.  * XEP-0092 (Software Version). You can configure the version reply for a given connection
  42.  * by running the following code:
  43.  * </p>
  44.  *
  45.  * <pre>
  46.  * Version MY_VERSION = new Version("My Little XMPP Application", "v1.23", "OS/2 32-bit");
  47.  * VersionManager.getInstanceFor(mConnection).setVersion(MY_VERSION);
  48.  * </pre>
  49.  *
  50.  * @author Georg Lukas
  51.  */
  52. public final class VersionManager extends Manager {
  53.     private static final Map<XMPPConnection, VersionManager> INSTANCES = new WeakHashMap<>();

  54.     private static VersionInformation defaultVersion;

  55.     private VersionInformation ourVersion = defaultVersion;

  56.     public static void setDefaultVersion(String name, String version) {
  57.         setDefaultVersion(name, version, null);
  58.     }

  59.     public static void setDefaultVersion(String name, String version, String os) {
  60.         defaultVersion = generateVersionFrom(name, version, os);
  61.     }

  62.     private static boolean autoAppendSmackVersion = true;

  63.     static {
  64.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
  65.             @Override
  66.             public void connectionCreated(XMPPConnection connection) {
  67.                 VersionManager.getInstanceFor(connection);
  68.             }
  69.         });
  70.     }

  71.     private VersionManager(final XMPPConnection connection) {
  72.         super(connection);

  73.         ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
  74.         sdm.addFeature(Version.NAMESPACE);

  75.         connection.registerIQRequestHandler(new AbstractIqRequestHandler(Version.ELEMENT, Version.NAMESPACE, IQ.Type.get,
  76.                         Mode.async) {
  77.             @Override
  78.             public IQ handleIQRequest(IQ iqRequest) {
  79.                 if (ourVersion == null) {
  80.                     return IQ.createErrorResponse(iqRequest, Condition.not_acceptable);
  81.                 }

  82.                 Version versionRequest = (Version) iqRequest;
  83.                 Version versionResponse = Version.builder(versionRequest)
  84.                                 .setName(ourVersion.name)
  85.                                 .setVersion(ourVersion.version)
  86.                                 .setOs(ourVersion.os)
  87.                                 .build();
  88.                 return versionResponse;
  89.             }
  90.         });
  91.     }

  92.     public static synchronized VersionManager getInstanceFor(XMPPConnection connection) {
  93.         VersionManager versionManager = INSTANCES.get(connection);

  94.         if (versionManager == null) {
  95.             versionManager = new VersionManager(connection);
  96.             INSTANCES.put(connection, versionManager);
  97.         }

  98.         return versionManager;
  99.     }

  100.     public static void setAutoAppendSmackVersion(boolean autoAppendSmackVersion) {
  101.         VersionManager.autoAppendSmackVersion = autoAppendSmackVersion;
  102.     }

  103.     public void setVersion(String name, String version) {
  104.         setVersion(name, version, null);
  105.     }

  106.     public void setVersion(String name, String version, String os) {
  107.         ourVersion = generateVersionFrom(name, version, os);
  108.     }

  109.     public void unsetVersion() {
  110.         ourVersion = null;
  111.     }

  112.     public boolean isSupported(Jid jid) throws NoResponseException, XMPPErrorException,
  113.                     NotConnectedException, InterruptedException {
  114.         return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid,
  115.                         Version.NAMESPACE);
  116.     }

  117.     /**
  118.      * Request version information from a given JID.
  119.      *
  120.      * @param jid TODO javadoc me please
  121.      * @return the version information or {@code null} if not supported by JID
  122.      * @throws NoResponseException if there was no response from the remote entity.
  123.      * @throws XMPPErrorException if there was an XMPP error returned.
  124.      * @throws NotConnectedException if the XMPP connection is not connected.
  125.      * @throws InterruptedException if the calling thread was interrupted.
  126.      */
  127.     public Version getVersion(Jid jid) throws NoResponseException, XMPPErrorException,
  128.                     NotConnectedException, InterruptedException {
  129.         if (!isSupported(jid)) {
  130.             return null;
  131.         }
  132.         XMPPConnection connection = connection();
  133.         Version version = Version.builder(connection).to(jid).build();
  134.         return connection().sendIqRequestAndWaitForResponse(version);
  135.     }

  136.     private static VersionInformation generateVersionFrom(String name, String version, String os) {
  137.         if (autoAppendSmackVersion) {
  138.             name += " (Smack " + Smack.getVersion() + ')';
  139.         }
  140.         return new VersionInformation(name, version, os);
  141.     }

  142.     private static final class VersionInformation {
  143.         private final String name;
  144.         private final String version;
  145.         private final String os;

  146.         private VersionInformation(String name, String version, String os) {
  147.             this.name = StringUtils.requireNotNullNorEmpty(name, "Must provide a name");
  148.             this.version = StringUtils.requireNotNullNorEmpty(version, "Must provide a version");
  149.             this.os = os;
  150.         }
  151.     }
  152. }