001/** 002 * 003 * Copyright 2014 Georg Lukas, 2021 Florian Schmaus. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.iqversion; 018 019import java.util.Map; 020import java.util.WeakHashMap; 021 022import org.jivesoftware.smack.ConnectionCreationListener; 023import org.jivesoftware.smack.Manager; 024import org.jivesoftware.smack.Smack; 025import org.jivesoftware.smack.SmackException.NoResponseException; 026import org.jivesoftware.smack.SmackException.NotConnectedException; 027import org.jivesoftware.smack.XMPPConnection; 028import org.jivesoftware.smack.XMPPConnectionRegistry; 029import org.jivesoftware.smack.XMPPException.XMPPErrorException; 030import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; 031import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; 032import org.jivesoftware.smack.packet.IQ; 033import org.jivesoftware.smack.packet.StanzaError.Condition; 034import org.jivesoftware.smack.util.StringUtils; 035 036import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 037import org.jivesoftware.smackx.iqversion.packet.Version; 038 039import org.jxmpp.jid.Jid; 040 041/** 042 * A Version Manager that automatically responds to version IQs with a predetermined reply. 043 * 044 * <p> 045 * The VersionManager takes care of handling incoming version request IQs, according to 046 * XEP-0092 (Software Version). You can configure the version reply for a given connection 047 * by running the following code: 048 * </p> 049 * 050 * <pre> 051 * Version MY_VERSION = new Version("My Little XMPP Application", "v1.23", "OS/2 32-bit"); 052 * VersionManager.getInstanceFor(mConnection).setVersion(MY_VERSION); 053 * </pre> 054 * 055 * @author Georg Lukas 056 */ 057public final class VersionManager extends Manager { 058 private static final Map<XMPPConnection, VersionManager> INSTANCES = new WeakHashMap<>(); 059 060 private static VersionInformation defaultVersion; 061 062 private VersionInformation ourVersion = defaultVersion; 063 064 public static void setDefaultVersion(String name, String version) { 065 setDefaultVersion(name, version, null); 066 } 067 068 public static void setDefaultVersion(String name, String version, String os) { 069 defaultVersion = generateVersionFrom(name, version, os); 070 } 071 072 private static boolean autoAppendSmackVersion = true; 073 074 static { 075 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 076 @Override 077 public void connectionCreated(XMPPConnection connection) { 078 VersionManager.getInstanceFor(connection); 079 } 080 }); 081 } 082 083 private VersionManager(final XMPPConnection connection) { 084 super(connection); 085 086 ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection); 087 sdm.addFeature(Version.NAMESPACE); 088 089 connection.registerIQRequestHandler(new AbstractIqRequestHandler(Version.ELEMENT, Version.NAMESPACE, IQ.Type.get, 090 Mode.async) { 091 @Override 092 public IQ handleIQRequest(IQ iqRequest) { 093 if (ourVersion == null) { 094 return IQ.createErrorResponse(iqRequest, Condition.not_acceptable); 095 } 096 097 Version versionRequest = (Version) iqRequest; 098 Version versionResponse = Version.builder(versionRequest) 099 .setName(ourVersion.name) 100 .setVersion(ourVersion.version) 101 .setOs(ourVersion.os) 102 .build(); 103 return versionResponse; 104 } 105 }); 106 } 107 108 public static synchronized VersionManager getInstanceFor(XMPPConnection connection) { 109 VersionManager versionManager = INSTANCES.get(connection); 110 111 if (versionManager == null) { 112 versionManager = new VersionManager(connection); 113 INSTANCES.put(connection, versionManager); 114 } 115 116 return versionManager; 117 } 118 119 public static void setAutoAppendSmackVersion(boolean autoAppendSmackVersion) { 120 VersionManager.autoAppendSmackVersion = autoAppendSmackVersion; 121 } 122 123 public void setVersion(String name, String version) { 124 setVersion(name, version, null); 125 } 126 127 public void setVersion(String name, String version, String os) { 128 ourVersion = generateVersionFrom(name, version, os); 129 } 130 131 public void unsetVersion() { 132 ourVersion = null; 133 } 134 135 public boolean isSupported(Jid jid) throws NoResponseException, XMPPErrorException, 136 NotConnectedException, InterruptedException { 137 return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid, 138 Version.NAMESPACE); 139 } 140 141 /** 142 * Request version information from a given JID. 143 * 144 * @param jid TODO javadoc me please 145 * @return the version information or {@code null} if not supported by JID 146 * @throws NoResponseException if there was no response from the remote entity. 147 * @throws XMPPErrorException if there was an XMPP error returned. 148 * @throws NotConnectedException if the XMPP connection is not connected. 149 * @throws InterruptedException if the calling thread was interrupted. 150 */ 151 public Version getVersion(Jid jid) throws NoResponseException, XMPPErrorException, 152 NotConnectedException, InterruptedException { 153 if (!isSupported(jid)) { 154 return null; 155 } 156 XMPPConnection connection = connection(); 157 Version version = Version.builder(connection).to(jid).build(); 158 return connection().sendIqRequestAndWaitForResponse(version); 159 } 160 161 private static VersionInformation generateVersionFrom(String name, String version, String os) { 162 if (autoAppendSmackVersion) { 163 name += " (Smack " + Smack.getVersion() + ')'; 164 } 165 return new VersionInformation(name, version, os); 166 } 167 168 private static final class VersionInformation { 169 private final String name; 170 private final String version; 171 private final String os; 172 173 private VersionInformation(String name, String version, String os) { 174 this.name = StringUtils.requireNotNullNorEmpty(name, "Must provide a name"); 175 this.version = StringUtils.requireNotNullNorEmpty(version, "Must provide a version"); 176 this.os = os; 177 } 178 } 179}