001/** 002 * 003 * Copyright 2003-2007 Jive Software. 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 */ 017 018package org.jivesoftware.smack; 019 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.WeakHashMap; 025 026import org.jivesoftware.smack.SmackException.NoResponseException; 027import org.jivesoftware.smack.SmackException.NotConnectedException; 028import org.jivesoftware.smack.XMPPException.XMPPErrorException; 029import org.jivesoftware.smack.packet.IQ; 030import org.jivesoftware.smack.packet.Registration; 031import org.jivesoftware.smack.util.StringUtils; 032 033/** 034 * Allows creation and management of accounts on an XMPP server. 035 * 036 * @author Matt Tucker 037 */ 038public class AccountManager extends Manager { 039 private static final Map<XMPPConnection, AccountManager> INSTANCES = new WeakHashMap<XMPPConnection, AccountManager>(); 040 041 /** 042 * Returns the AccountManager instance associated with a given XMPPConnection. 043 * 044 * @param connection the connection used to look for the proper ServiceDiscoveryManager. 045 * @return the AccountManager associated with a given XMPPConnection. 046 */ 047 public static synchronized AccountManager getInstance(XMPPConnection connection) { 048 AccountManager accountManager = INSTANCES.get(connection); 049 if (accountManager == null) 050 accountManager = new AccountManager(connection); 051 return accountManager; 052 } 053 054 private Registration info = null; 055 056 /** 057 * Flag that indicates whether the server supports In-Band Registration. 058 * In-Band Registration may be advertised as a stream feature. If no stream feature 059 * was advertised from the server then try sending an IQ packet to discover if In-Band 060 * Registration is available. 061 */ 062 private boolean accountCreationSupported = false; 063 064 /** 065 * Creates a new AccountManager instance. 066 * 067 * @param connection a connection to a XMPP server. 068 */ 069 private AccountManager(XMPPConnection connection) { 070 super(connection); 071 INSTANCES.put(connection, this); 072 } 073 074 /** 075 * Sets whether the server supports In-Band Registration. In-Band Registration may be 076 * advertised as a stream feature. If no stream feature was advertised from the server 077 * then try sending an IQ packet to discover if In-Band Registration is available. 078 * 079 * @param accountCreationSupported true if the server supports In-Band Registration. 080 */ 081 void setSupportsAccountCreation(boolean accountCreationSupported) { 082 this.accountCreationSupported = accountCreationSupported; 083 } 084 085 /** 086 * Returns true if the server supports creating new accounts. Many servers require 087 * that you not be currently authenticated when creating new accounts, so the safest 088 * behavior is to only create new accounts before having logged in to a server. 089 * 090 * @return true if the server support creating new accounts. 091 * @throws XMPPErrorException 092 * @throws NoResponseException 093 * @throws NotConnectedException 094 */ 095 public boolean supportsAccountCreation() throws NoResponseException, XMPPErrorException, NotConnectedException { 096 // Check if we already know that the server supports creating new accounts 097 if (accountCreationSupported) { 098 return true; 099 } 100 // No information is known yet (e.g. no stream feature was received from the server 101 // indicating that it supports creating new accounts) so send an IQ packet as a way 102 // to discover if this feature is supported 103 if (info == null) { 104 getRegistrationInfo(); 105 accountCreationSupported = info.getType() != IQ.Type.ERROR; 106 } 107 return accountCreationSupported; 108 } 109 110 /** 111 * Returns an unmodifiable collection of the names of the required account attributes. 112 * All attributes must be set when creating new accounts. The standard set of possible 113 * attributes are as follows: <ul> 114 * <li>name -- the user's name. 115 * <li>first -- the user's first name. 116 * <li>last -- the user's last name. 117 * <li>email -- the user's email address. 118 * <li>city -- the user's city. 119 * <li>state -- the user's state. 120 * <li>zip -- the user's ZIP code. 121 * <li>phone -- the user's phone number. 122 * <li>url -- the user's website. 123 * <li>date -- the date the registration took place. 124 * <li>misc -- other miscellaneous information to associate with the account. 125 * <li>text -- textual information to associate with the account. 126 * <li>remove -- empty flag to remove account. 127 * </ul><p> 128 * 129 * Typically, servers require no attributes when creating new accounts, or just 130 * the user's email address. 131 * 132 * @return the required account attributes. 133 * @throws XMPPErrorException 134 * @throws NoResponseException 135 * @throws NotConnectedException 136 */ 137 public Collection<String> getAccountAttributes() throws NoResponseException, XMPPErrorException, NotConnectedException { 138 if (info == null) { 139 getRegistrationInfo(); 140 } 141 Map<String, String> attributes = info.getAttributes(); 142 if (attributes != null) { 143 return Collections.unmodifiableSet(attributes.keySet()); 144 } else { 145 return Collections.emptySet(); 146 } 147 } 148 149 /** 150 * Returns the value of a given account attribute or <tt>null</tt> if the account 151 * attribute wasn't found. 152 * 153 * @param name the name of the account attribute to return its value. 154 * @return the value of the account attribute or <tt>null</tt> if an account 155 * attribute wasn't found for the requested name. 156 * @throws XMPPErrorException 157 * @throws NoResponseException 158 * @throws NotConnectedException 159 */ 160 public String getAccountAttribute(String name) throws NoResponseException, XMPPErrorException, NotConnectedException { 161 if (info == null) { 162 getRegistrationInfo(); 163 } 164 return info.getAttributes().get(name); 165 } 166 167 /** 168 * Returns the instructions for creating a new account, or <tt>null</tt> if there 169 * are no instructions. If present, instructions should be displayed to the end-user 170 * that will complete the registration process. 171 * 172 * @return the account creation instructions, or <tt>null</tt> if there are none. 173 * @throws XMPPErrorException 174 * @throws NoResponseException 175 * @throws NotConnectedException 176 */ 177 public String getAccountInstructions() throws NoResponseException, XMPPErrorException, NotConnectedException { 178 if (info == null) { 179 getRegistrationInfo(); 180 } 181 return info.getInstructions(); 182 } 183 184 /** 185 * Creates a new account using the specified username and password. The server may 186 * require a number of extra account attributes such as an email address and phone 187 * number. In that case, Smack will attempt to automatically set all required 188 * attributes with blank values, which may or may not be accepted by the server. 189 * Therefore, it's recommended to check the required account attributes and to let 190 * the end-user populate them with real values instead. 191 * 192 * @param username the username. 193 * @param password the password. 194 * @throws XMPPErrorException 195 * @throws NoResponseException 196 * @throws NotConnectedException 197 */ 198 public void createAccount(String username, String password) throws NoResponseException, XMPPErrorException, NotConnectedException { 199 // Create a map for all the required attributes, but give them blank values. 200 Map<String, String> attributes = new HashMap<String, String>(); 201 for (String attributeName : getAccountAttributes()) { 202 attributes.put(attributeName, ""); 203 } 204 createAccount(username, password, attributes); 205 } 206 207 /** 208 * Creates a new account using the specified username, password and account attributes. 209 * The attributes Map must contain only String name/value pairs and must also have values 210 * for all required attributes. 211 * 212 * @param username the username. 213 * @param password the password. 214 * @param attributes the account attributes. 215 * @throws XMPPErrorException if an error occurs creating the account. 216 * @throws NoResponseException if there was no response from the server. 217 * @throws NotConnectedException 218 * @see #getAccountAttributes() 219 */ 220 public void createAccount(String username, String password, Map<String, String> attributes) 221 throws NoResponseException, XMPPErrorException, NotConnectedException { 222 Registration reg = new Registration(); 223 reg.setType(IQ.Type.SET); 224 reg.setTo(connection().getServiceName()); 225 attributes.put("username", username); 226 attributes.put("password", password); 227 reg.setAttributes(attributes); 228 connection().createPacketCollectorAndSend(reg).nextResultOrThrow(); 229 } 230 231 /** 232 * Changes the password of the currently logged-in account. This operation can only 233 * be performed after a successful login operation has been completed. Not all servers 234 * support changing passwords; an XMPPException will be thrown when that is the case. 235 * 236 * @throws IllegalStateException if not currently logged-in to the server. 237 * @throws XMPPErrorException if an error occurs when changing the password. 238 * @throws NoResponseException if there was no response from the server. 239 * @throws NotConnectedException 240 */ 241 public void changePassword(String newPassword) throws NoResponseException, XMPPErrorException, NotConnectedException { 242 Registration reg = new Registration(); 243 reg.setType(IQ.Type.SET); 244 reg.setTo(connection().getServiceName()); 245 Map<String, String> map = new HashMap<String, String>(); 246 map.put("username",StringUtils.parseName(connection().getUser())); 247 map.put("password",newPassword); 248 reg.setAttributes(map); 249 connection().createPacketCollectorAndSend(reg).nextResultOrThrow(); 250 } 251 252 /** 253 * Deletes the currently logged-in account from the server. This operation can only 254 * be performed after a successful login operation has been completed. Not all servers 255 * support deleting accounts; an XMPPException will be thrown when that is the case. 256 * 257 * @throws IllegalStateException if not currently logged-in to the server. 258 * @throws XMPPErrorException if an error occurs when deleting the account. 259 * @throws NoResponseException if there was no response from the server. 260 * @throws NotConnectedException 261 */ 262 public void deleteAccount() throws NoResponseException, XMPPErrorException, NotConnectedException { 263 Registration reg = new Registration(); 264 reg.setType(IQ.Type.SET); 265 reg.setTo(connection().getServiceName()); 266 Map<String, String> attributes = new HashMap<String, String>(); 267 // To delete an account, we add a single attribute, "remove", that is blank. 268 attributes.put("remove", ""); 269 reg.setAttributes(attributes); 270 connection().createPacketCollectorAndSend(reg).nextResultOrThrow(); 271 } 272 273 /** 274 * Gets the account registration info from the server. 275 * @throws XMPPErrorException 276 * @throws NoResponseException 277 * @throws NotConnectedException 278 * 279 * @throws XMPPException if an error occurs. 280 * @throws SmackException if there was no response from the server. 281 */ 282 private synchronized void getRegistrationInfo() throws NoResponseException, XMPPErrorException, NotConnectedException { 283 Registration reg = new Registration(); 284 reg.setTo(connection().getServiceName()); 285 info = (Registration) connection().createPacketCollectorAndSend(reg).nextResultOrThrow(); 286 } 287}