1 package org.jivesoftware.smackx; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.List; 6 import java.util.Map; 7 8 import org.jivesoftware.smack.Connection; 9 import org.jivesoftware.smack.PacketCollector; 10 import org.jivesoftware.smack.PacketListener; 11 import org.jivesoftware.smack.Roster; 12 import org.jivesoftware.smack.RosterEntry; 13 import org.jivesoftware.smack.SmackConfiguration; 14 import org.jivesoftware.smack.XMPPException; 15 import org.jivesoftware.smack.filter.PacketIDFilter; 16 import org.jivesoftware.smack.filter.PacketTypeFilter; 17 import org.jivesoftware.smack.packet.IQ; 18 import org.jivesoftware.smack.packet.Packet; 19 import org.jivesoftware.smack.packet.Presence; 20 import org.jivesoftware.smack.packet.Registration; 21 import org.jivesoftware.smack.util.StringUtils; 22 import org.jivesoftware.smackx.packet.DiscoverInfo; 23 import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; 24 25 /** 26 * This class provides an abstract view to gateways/transports. This class handles all 27 * actions regarding gateways and transports. 28 * @author Till Klocke 29 * 30 */ 31 public class Gateway { 32 33 private Connection connection; 34 private ServiceDiscoveryManager sdManager; 35 private Roster roster; 36 private String entityJID; 37 private Registration registerInfo; 38 private Identity identity; 39 private DiscoverInfo info; 40 41 Gateway(Connection connection, String entityJID){ 42 this.connection = connection; 43 this.roster = connection.getRoster(); 44 this.sdManager = ServiceDiscoveryManager.getInstanceFor(connection); 45 this.entityJID = entityJID; 46 } 47 48 Gateway(Connection connection, String entityJID, DiscoverInfo info, Identity identity){ 49 this(connection, entityJID); 50 this.info = info; 51 this.identity = identity; 52 } 53 54 private void discoverInfo() throws XMPPException{ 55 info = sdManager.discoverInfo(entityJID); 56 Iterator<Identity> iterator = info.getIdentities(); 57 while(iterator.hasNext()){ 58 Identity temp = iterator.next(); 59 if(temp.getCategory().equalsIgnoreCase("gateway")){ 60 this.identity = temp; 61 break; 62 } 63 } 64 } 65 66 private Identity getIdentity() throws XMPPException{ 67 if(identity==null){ 68 discoverInfo(); 69 } 70 return identity; 71 } 72 73 private Registration getRegisterInfo(){ 74 if(registerInfo==null){ 75 refreshRegisterInfo(); 76 } 77 return registerInfo; 78 } 79 80 private void refreshRegisterInfo(){ 81 Registration packet = new Registration(); 82 packet.setFrom(connection.getUser()); 83 packet.setType(IQ.Type.GET); 84 packet.setTo(entityJID); 85 PacketCollector collector = 86 connection.createPacketCollector(new PacketIDFilter(packet.getPacketID())); 87 connection.sendPacket(packet); 88 Packet result = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 89 collector.cancel(); 90 if(result instanceof Registration && result.getError()==null){ 91 Registration register = (Registration)result; 92 this.registerInfo = register; 93 } 94 } 95 96 /** 97 * Checks if this gateway supports In-Band registration 98 * @return true if In-Band registration is supported 99 * @throws XMPPException 100 */ 101 public boolean canRegister() throws XMPPException{ 102 if(info==null){ 103 discoverInfo(); 104 } 105 return info.containsFeature("jabber:iq:register"); 106 } 107 108 /** 109 * Returns all fields that are required to register to this gateway 110 * @return a list of required fields 111 */ 112 public List<String> getRequiredFields(){ 113 return getRegisterInfo().getRequiredFields(); 114 } 115 116 /** 117 * Returns the name as proposed in this gateways identity discovered via service 118 * discovery 119 * @return a String of its name 120 * @throws XMPPException 121 */ 122 public String getName() throws XMPPException{ 123 if(identity==null){ 124 discoverInfo(); 125 } 126 return identity.getName(); 127 } 128 129 /** 130 * Returns the type as proposed in this gateways identity discovered via service 131 * discovery. See {@link http://xmpp.org/registrar/disco-categories.html} for 132 * possible types 133 * @return a String describing the type 134 * @throws XMPPException 135 */ 136 public String getType() throws XMPPException{ 137 if(identity==null){ 138 discoverInfo(); 139 } 140 return identity.getType(); 141 } 142 143 /** 144 * Returns true if the registration informations indicates that you are already 145 * registered with this gateway 146 * @return true if already registered 147 * @throws XMPPException 148 */ 149 public boolean isRegistered() throws XMPPException{ 150 return getRegisterInfo().isRegistered(); 151 } 152 153 /** 154 * Returns the value of specific field of the registration information. Can be used 155 * to retrieve for example to retrieve username/password used on an already registered 156 * gateway. 157 * @param fieldName name of the field 158 * @return a String containing the value of the field or null 159 */ 160 public String getField(String fieldName){ 161 return getRegisterInfo().getField(fieldName); 162 } 163 164 /** 165 * Returns a List of Strings of all field names which contain values. 166 * @return a List of field names 167 */ 168 public List<String> getFieldNames(){ 169 return getRegisterInfo().getFieldNames(); 170 } 171 172 /** 173 * A convenience method for retrieving the username of an existing account 174 * @return String describing the username 175 */ 176 public String getUsername(){ 177 return getField("username"); 178 } 179 180 /** 181 * A convenience method for retrieving the password of an existing accoung 182 * @return String describing the password 183 */ 184 public String getPassword(){ 185 return getField("password"); 186 } 187 188 /** 189 * Returns instructions for registering with this gateway 190 * @return String containing instructions 191 */ 192 public String getInstructions(){ 193 return getRegisterInfo().getInstructions(); 194 } 195 196 /** 197 * With this method you can register with this gateway or modify an existing registration 198 * @param username String describing the username 199 * @param password String describing the password 200 * @param fields additional fields like email. 201 * @throws XMPPException 202 */ 203 public void register(String username, String password, Map<String,String> fields)throws XMPPException{ 204 if(getRegisterInfo().isRegistered()) { 205 throw new IllegalStateException("You are already registered with this gateway"); 206 } 207 Registration register = new Registration(); 208 register.setFrom(connection.getUser()); 209 register.setTo(entityJID); 210 register.setType(IQ.Type.SET); 211 register.setUsername(username); 212 register.setPassword(password); 213 for(String s : fields.keySet()){ 214 register.addAttribute(s, fields.get(s)); 215 } 216 PacketCollector resultCollector = 217 connection.createPacketCollector(new PacketIDFilter(register.getPacketID())); 218 connection.sendPacket(register); 219 Packet result = 220 resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 221 resultCollector.cancel(); 222 if(result!=null && result instanceof IQ){ 223 IQ resultIQ = (IQ)result; 224 if(resultIQ.getError()!=null){ 225 throw new XMPPException(resultIQ.getError()); 226 } 227 if(resultIQ.getType()==IQ.Type.ERROR){ 228 throw new XMPPException(resultIQ.getError()); 229 } 230 connection.addPacketListener(new GatewayPresenceListener(), 231 new PacketTypeFilter(Presence.class)); 232 roster.createEntry(entityJID, getIdentity().getName(), new String[]{}); 233 } 234 else{ 235 throw new XMPPException("Packet reply timeout"); 236 } 237 } 238 239 /** 240 * A convenience method for registering or modifying an account on this gateway without 241 * additional fields 242 * @param username String describing the username 243 * @param password String describing the password 244 * @throws XMPPException 245 */ 246 public void register(String username, String password) throws XMPPException{ 247 register(username, password,new HashMap<String,String>()); 248 } 249 250 /** 251 * This method removes an existing registration from this gateway 252 * @throws XMPPException 253 */ 254 public void unregister() throws XMPPException{ 255 Registration register = new Registration(); 256 register.setFrom(connection.getUser()); 257 register.setTo(entityJID); 258 register.setType(IQ.Type.SET); 259 register.setRemove(true); 260 PacketCollector resultCollector = 261 connection.createPacketCollector(new PacketIDFilter(register.getPacketID())); 262 connection.sendPacket(register); 263 Packet result = resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 264 resultCollector.cancel(); 265 if(result!=null && result instanceof IQ){ 266 IQ resultIQ = (IQ)result; 267 if(resultIQ.getError()!=null){ 268 throw new XMPPException(resultIQ.getError()); 269 } 270 if(resultIQ.getType()==IQ.Type.ERROR){ 271 throw new XMPPException(resultIQ.getError()); 272 } 273 RosterEntry gatewayEntry = roster.getEntry(entityJID); 274 roster.removeEntry(gatewayEntry); 275 } 276 else{ 277 throw new XMPPException("Packet reply timeout"); 278 } 279 } 280 281 /** 282 * Lets you login manually in this gateway. Normally a gateway logins you when it 283 * receives the first presence broadcasted by your server. But it is possible to 284 * manually login and logout by sending a directed presence. This method sends an 285 * empty available presence direct to the gateway. 286 */ 287 public void login(){ 288 Presence presence = new Presence(Presence.Type.available); 289 login(presence); 290 } 291 292 /** 293 * This method lets you send the presence direct to the gateway. Type, To and From 294 * are modified. 295 * @param presence the presence used to login to gateway 296 */ 297 public void login(Presence presence){ 298 presence.setType(Presence.Type.available); 299 presence.setTo(entityJID); 300 presence.setFrom(connection.getUser()); 301 connection.sendPacket(presence); 302 } 303 304 /** 305 * This method logs you out from this gateway by sending an unavailable presence 306 * to directly to this gateway. 307 */ 308 public void logout(){ 309 Presence presence = new Presence(Presence.Type.unavailable); 310 presence.setTo(entityJID); 311 presence.setFrom(connection.getUser()); 312 connection.sendPacket(presence); 313 } 314 315 private class GatewayPresenceListener implements PacketListener{ 316 317 public void processPacket(Packet packet) { 318 if(packet instanceof Presence){ 319 Presence presence = (Presence)packet; 320 if(entityJID.equals(presence.getFrom()) && 321 roster.contains(presence.getFrom()) && 322 presence.getType().equals(Presence.Type.subscribe)){ 323 Presence response = new Presence(Presence.Type.subscribed); 324 response.setTo(presence.getFrom()); 325 response.setFrom(StringUtils.parseBareAddress(connection.getUser())); 326 connection.sendPacket(response); 327 } 328 } 329 330 } 331 } 332 333 } 334