From 7fa5b3608ecd616e1bb8f3a4021e82123d49dc37 Mon Sep 17 00:00:00 2001 From: Matt Tucker Date: Sat, 8 Mar 2003 23:09:48 +0000 Subject: [PATCH] Initial account manager functionality. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@1846 b35dd754-fafc-0310-a699-88a17e54d16e --- .../jivesoftware/smack/AccountManager.java | 109 ++++++++++++ .../org/jivesoftware/smack/PacketReader.java | 45 ++++- .../jivesoftware/smack/XMPPConnection.java | 59 +++++-- .../org/jivesoftware/smack/XMPPException.java | 13 +- .../smack/packet/Registration.java | 163 ++++++++++++++++++ .../jivesoftware/smack/packet/XMPPError.java | 4 +- 6 files changed, 362 insertions(+), 31 deletions(-) create mode 100644 source/org/jivesoftware/smack/AccountManager.java create mode 100644 source/org/jivesoftware/smack/packet/Registration.java diff --git a/source/org/jivesoftware/smack/AccountManager.java b/source/org/jivesoftware/smack/AccountManager.java new file mode 100644 index 000000000..c538dddc1 --- /dev/null +++ b/source/org/jivesoftware/smack/AccountManager.java @@ -0,0 +1,109 @@ +package org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Registration; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.filter.*; + +import java.util.*; + +/** + * Allows creation and management of accounts on an XMPP server. + * + * @see XMPPConnection#getAccountManager(); + * @author Matt Tucker + */ +public class AccountManager { + + private XMPPConnection connection; + private Registration info = null; + + public AccountManager(XMPPConnection connection) { + this.connection = connection; + } + + /** + * Returns true if the server supports creating new accounts. Many servers require that + * you not be currently authenticated when creating new accounts, so the safest + * behavior is to only create new accounts when + * + * @return true if the server support creating new accounts. + */ + public boolean supportsAccountCreation() { + try { + if (info == null) { + getRegistrationInfo(); + } + return info.getType() != IQ.Type.ERROR; + } + catch (XMPPException xe) { + return false; + } + } + + public Iterator getAccountAttributes() { + try { + if (info == null) { + getRegistrationInfo(); + } + Map attributes = info.getAttributes(); + if (attributes != null) { + return attributes.keySet().iterator(); + } + } + catch (XMPPException xe) { } + return Collections.EMPTY_LIST.iterator(); + } + + public void createAccount(String username, String password) throws XMPPException { + if (!supportsAccountCreation()) { + throw new XMPPException("Server does not support account creation."); + } + } + + public void createAccount(String username, String password, Map attributes) + throws XMPPException + { + + } + + public void deleteAccount() throws XMPPException { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to delete a account."); + } + Registration reg = new Registration(); + reg.setType(IQ.Type.SET); + Map attributes = new HashMap(); + // To delete an account, we add a single attribute, "remove", that is blank. + attributes.put("remove", ""); + reg.setAttributes(attributes); + PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), + new PacketTypeFilter(Registration.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ)collector.nextResult(5000); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } + + private synchronized void getRegistrationInfo() throws XMPPException { + Registration reg = new Registration(); + PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), + new PacketTypeFilter(IQ.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ)collector.nextResult(5000); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + else { + info = (Registration)result; + } + } +} \ No newline at end of file diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index 7cf62d1c5..353808725 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -342,6 +342,9 @@ class PacketReader { else if (namespace.equals("jabber:iq:roster")) { iqPacket = parseRoster(parser); } + else if (namespace.equals("jabber:iq:register")) { + iqPacket = parseRegistration(parser); + } } else if (parser.getName().equals("error")) { error = parseError(parser); @@ -437,6 +440,41 @@ class PacketReader { return roster; } + private static Registration parseRegistration(XmlPullParser parser) throws Exception { + Registration registration = new Registration(); + Map fields = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == parser.START_TAG) { + if (parser.getName().equals("username")) { + registration.setUsername(parser.nextText()); + } + else if (parser.getName().equals("password")) { + registration.setPassword(parser.nextText()); + } + else { + String name = parser.getName(); + String value = parser.nextText(); + // Ignore instructions, but anything else should be added to the map. + if (!name.equals("instructions")) { + if (fields == null) { + fields = new HashMap(); + } + fields.put(name, value); + } + } + } + else if (eventType == parser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + registration.setAttributes(fields); + return registration; + } + private static XMPPError parseError(XmlPullParser parser) throws Exception { String errorCode = null; for (int i=0; i * - * Every connection has a PacketReader and PacketWriter instance, which are used - * to read and write XML with the server. - * * @author Matt Tucker */ public class XMPPConnection { @@ -106,11 +102,13 @@ public class XMPPConnection { String connectionID; private boolean connected = false; + private boolean authenticated = false; private PacketWriter packetWriter; private PacketReader packetReader; - private Roster roster; + private Roster roster = null; + private AccountManager accountManager = null; Writer writer; Reader reader; @@ -209,6 +207,8 @@ public class XMPPConnection { * @param password the password. * @param resource the resource. * @throws XMPPException if an error occurs. + * @throws IllegalStateException if not connected to the server, or already logged in + * to the serrver. */ public synchronized void login(String username, String password, String resource) throws XMPPException @@ -216,6 +216,9 @@ public class XMPPConnection { if (!isConnected()) { throw new IllegalStateException("Not connected to server."); } + if (authenticated) { + throw new IllegalStateException("Already logged in to server."); + } // If we send an authentication packet in "get" mode with just the username, // the server will return the list of authentication protocols it supports. Authentication discoveryAuth = new Authentication(); @@ -263,17 +266,7 @@ public class XMPPConnection { throw new XMPPException("Authentication failed."); } else if (response.getType() == IQ.Type.ERROR) { - if (response.getError() == null) { - throw new XMPPException("Authentication failed."); - } - else { - XMPPError error = response.getError(); - String msg = "Authentication failed -- " + error.getCode(); - if (error.getMessage() != null) { - msg += ": " + error.getMessage(); - } - throw new XMPPException(msg); - } + throw new XMPPException(response.getError()); } // We're done with the collector, so explicitly cancel it. collector.cancel(); @@ -284,6 +277,9 @@ public class XMPPConnection { this.roster = new Roster(this); roster.reload(); + // Indicate that we're now authenticated. + authenticated = true; + // If debugging is enabled, change the the debug window title to include the // name we are now logged-in as. if (DEBUG_ENABLED) { @@ -295,10 +291,27 @@ public class XMPPConnection { } } + /** + * Returns the roster for the user logged into the server. If the user has not yet + * logged into the server, this method will return null. + * + * @return the user's roster, or null if the user has not logged in yet. + */ public Roster getRoster() { return roster; } + /** + * Returns an account manager instance for this + * @return + */ + public synchronized AccountManager getAccountManager() { + if (accountManager == null) { + accountManager = new AccountManager(this); + } + return accountManager; + } + /** * Creates a new chat with the specified participant. The participant should * be a valid Jabber user such as jdoe@jivesoftware.com or @@ -329,7 +342,7 @@ public class XMPPConnection { } /** - * Returns true if currently connected to the Jabber server. + * Returns true if currently connected to the XMPP server. * * @return true if connected. */ @@ -337,6 +350,15 @@ public class XMPPConnection { return connected; } + /** + * Returns true if currently authenticated by successfully calling the login method. + * + * @return true if authenticated. + */ + public boolean isAuthenticated() { + return authenticated; + } + /** * Closes the connection by setting presence to unavailable then closing the stream to * the XMPP server. Once a connection has been closed, it cannot be re-opened. @@ -355,6 +377,7 @@ public class XMPPConnection { socket.close(); } catch (Exception e) { } + authenticated = false; connected = false; } diff --git a/source/org/jivesoftware/smack/XMPPException.java b/source/org/jivesoftware/smack/XMPPException.java index 678ac2e86..a9e16b98b 100644 --- a/source/org/jivesoftware/smack/XMPPException.java +++ b/source/org/jivesoftware/smack/XMPPException.java @@ -97,19 +97,12 @@ public class XMPPException extends Exception { } public void printStackTrace() { - if (error != null) { - System.err.println(error); - } - super.printStackTrace(); - if (cause != null) { - System.err.println("Nested Exception: "); - cause.printStackTrace(); - } + printStackTrace(System.err); } public void printStackTrace(PrintStream out) { if (error != null) { - System.err.println(error); + System.err.print(error + " -- "); } super.printStackTrace(out); if (cause != null) { @@ -120,7 +113,7 @@ public class XMPPException extends Exception { public void printStackTrace(PrintWriter out) { if (error != null) { - System.err.println(error); + System.err.print(error + " -- "); } super.printStackTrace(out); if (cause != null) { diff --git a/source/org/jivesoftware/smack/packet/Registration.java b/source/org/jivesoftware/smack/packet/Registration.java new file mode 100644 index 000000000..48416695c --- /dev/null +++ b/source/org/jivesoftware/smack/packet/Registration.java @@ -0,0 +1,163 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.packet; + +import java.util.Map; +import java.util.Iterator; + +/** + * Represents registration packets. An empty GET query will cause the server to return information + * about it's registration support. SET queries can be used to create accounts or update + * existing account information. XMPP servers may require a number of attributes to be set + * when creating a new account. The standard account attributes are as follows: + * + * + * @author Matt Tucker + */ +public class Registration extends IQ { + + private String username = null; + private String password = null; + private Map attributes = null; + + /** + * Returns the username, or null if no username has ben set. + * + * @return the username. + */ + public String getUsername() { + return username; + } + + /** + * Sets the username. + * + * @param username the username. + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Returns the password, or null if no password has been set. + * + * @return the password. + */ + public String getPassword() { + return password; + } + + /** + * Sets the password. + * + * @param password the password. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Returns the map of String key/value pairs of account attributes. + * + * @return the account attributes. + */ + public Map getAttributes() { + return attributes; + } + + /** + * Sets the account attributes. The map must only contain String key/value pairs. + * + * @param attributes the account attributes. + */ + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public String getQueryXML() { + StringBuffer buf = new StringBuffer(); + buf.append(""); + if (username != null) { + buf.append("").append(username).append(""); + } + if (password != null) { + buf.append("").append(password).append(""); + } + if (attributes != null && attributes.size() > 0) { + Iterator fieldNames = attributes.keySet().iterator(); + while (fieldNames.hasNext()) { + String name = (String)fieldNames.next(); + String value = (String)attributes.get(name); + buf.append("<").append(name).append(">"); + buf.append(value); + buf.append(""); + } + } + buf.append(""); + return buf.toString(); + } +} diff --git a/source/org/jivesoftware/smack/packet/XMPPError.java b/source/org/jivesoftware/smack/packet/XMPPError.java index c9167cce5..c9dcc1537 100644 --- a/source/org/jivesoftware/smack/packet/XMPPError.java +++ b/source/org/jivesoftware/smack/packet/XMPPError.java @@ -141,9 +141,9 @@ public class XMPPError { public String toString() { StringBuffer txt = new StringBuffer(); - txt.append(code); + txt.append("(").append(code).append(")"); if (message != null) { - txt.append(": ").append(message); + txt.append(" ").append(message); } return txt.toString(); }