User.java 14.9 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1 2 3 4 5
/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
Matt Tucker's avatar
Matt Tucker committed
6
 * Copyright (C) 2004 Jive Software. All rights reserved.
Matt Tucker's avatar
Matt Tucker committed
7
 *
Matt Tucker's avatar
Matt Tucker committed
8 9
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
Matt Tucker's avatar
Matt Tucker committed
10
 */
Matt Tucker's avatar
Matt Tucker committed
11

Matt Tucker's avatar
Matt Tucker committed
12 13
package org.jivesoftware.messenger.user;

14
import org.jivesoftware.messenger.roster.Roster;
15
import org.jivesoftware.messenger.XMPPServer;
16
import org.jivesoftware.messenger.event.UserEventDispatcher;
Matt Tucker's avatar
Matt Tucker committed
17 18 19 20 21 22
import org.jivesoftware.util.Log;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.database.DbConnectionManager;

import java.util.*;
23
import java.util.concurrent.ConcurrentHashMap;
Matt Tucker's avatar
Matt Tucker committed
24 25 26 27
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
Matt Tucker's avatar
Matt Tucker committed
28 29

/**
Matt Tucker's avatar
Matt Tucker committed
30 31 32 33 34 35
 * Encapsulates information about a user. New users are created using
 * {@link UserManager#createUser(String, String, String, String)}. All user
 * properties are loaded on demand and are read from the <tt>jiveUserProp</tt>
 * database table. The currently-installed {@link UserProvider} is used for
 * setting all other user data and some operations may not be supported
 * depending on the capabilities of the {@link UserProvider}.
Matt Tucker's avatar
Matt Tucker committed
36
 *
Matt Tucker's avatar
Matt Tucker committed
37
 * @author Matt Tucker
Matt Tucker's avatar
Matt Tucker committed
38
 */
Matt Tucker's avatar
Matt Tucker committed
39
public class User implements Cacheable {
40

Matt Tucker's avatar
Matt Tucker committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
    private static final String LOAD_PROPERTIES =
        "SELECT name, propValue FROM jiveUserProp WHERE username=?";
    private static final String DELETE_PROPERTY =
        "DELETE FROM jiveUserProp WHERE username=? AND name=?";
    private static final String UPDATE_PROPERTY =
        "UPDATE jiveUserProp SET propValue=? WHERE name=? AND username=?";
    private static final String INSERT_PROPERTY =
        "INSERT INTO jiveUserProp (username, name, propValue) VALUES (?, ?, ?)";

    private String username;
    private String name;
    private String email;
    private Date creationDate;
    private Date modificationDate;

    private Map<String,String> properties = null;
Matt Tucker's avatar
Matt Tucker committed
57 58

    /**
Matt Tucker's avatar
Matt Tucker committed
59 60 61
     * Constructs a new user. All arguments can be <tt>null</tt> except the username.
     * Typically, User objects should not be constructed by end-users of the API.
     * Instead, user objects should be retrieved using {@link UserManager#getUser(String)}.
Matt Tucker's avatar
Matt Tucker committed
62
     *
Matt Tucker's avatar
Matt Tucker committed
63 64 65 66 67
     * @param username the username.
     * @param name the name.
     * @param email the email address.
     * @param creationDate the date the user was created.
     * @param modificationDate the date the user was last modified.
Matt Tucker's avatar
Matt Tucker committed
68
     */
Matt Tucker's avatar
Matt Tucker committed
69 70 71 72 73 74 75 76 77 78 79 80
    public User(String username, String name, String email, Date creationDate,
            Date modificationDate)
    {
        if (username == null) {
            throw new NullPointerException("Username cannot be null");
        }
        this.username = username;
        this.name = name;
        this.email = email;
        this.creationDate = creationDate;
        this.modificationDate = modificationDate;
    }
Matt Tucker's avatar
Matt Tucker committed
81 82

    /**
Matt Tucker's avatar
Matt Tucker committed
83
     * Returns this user's username.
Matt Tucker's avatar
Matt Tucker committed
84
     *
Matt Tucker's avatar
Matt Tucker committed
85
     * @return the username..
Matt Tucker's avatar
Matt Tucker committed
86
     */
Matt Tucker's avatar
Matt Tucker committed
87 88 89
    public String getUsername() {
        return username;
    }
Matt Tucker's avatar
Matt Tucker committed
90 91

    /**
Matt Tucker's avatar
Matt Tucker committed
92
     * Sets a new password for this user.
Matt Tucker's avatar
Matt Tucker committed
93
     *
Matt Tucker's avatar
Matt Tucker committed
94
     * @param password the new password for the user.
Matt Tucker's avatar
Matt Tucker committed
95
     */
Matt Tucker's avatar
Matt Tucker committed
96
    public void setPassword(String password) {
97 98 99 100
        if (UserManager.getUserProvider().isReadOnly()) {
            throw new UnsupportedOperationException("User provider is read-only.");
        }

Matt Tucker's avatar
Matt Tucker committed
101 102
        try {
            UserManager.getUserProvider().setPassword(username, password);
103 104 105 106 107 108

            // Fire event.
            Map params = new HashMap();
            params.put("type", "passwordModified");
            UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
                    params);
Matt Tucker's avatar
Matt Tucker committed
109 110 111 112 113 114 115
        }
        catch (UserNotFoundException unfe) {
            Log.error(unfe);
        }
    }

    public String getName() {
116
        return name == null ? "" : name;
Matt Tucker's avatar
Matt Tucker committed
117 118 119
    }

    public void setName(String name) {
120 121 122 123
        if (UserManager.getUserProvider().isReadOnly()) {
            throw new UnsupportedOperationException("User provider is read-only.");
        }

Matt Tucker's avatar
Matt Tucker committed
124
        try {
125
            String originalName = this.name;
Matt Tucker's avatar
Matt Tucker committed
126 127
            UserManager.getUserProvider().setName(username, name);
            this.name = name;
128 129 130 131 132 133 134

            // Fire event.
            Map params = new HashMap();
            params.put("type", "nameModified");
            params.put("originalValue", originalName);
            UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
                    params);
Matt Tucker's avatar
Matt Tucker committed
135 136 137 138 139 140 141 142 143 144 145
        }
        catch (UserNotFoundException unfe) {
            Log.error(unfe);
        }
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
146 147 148 149
        if (UserManager.getUserProvider().isReadOnly()) {
            throw new UnsupportedOperationException("User provider is read-only.");
        }

Matt Tucker's avatar
Matt Tucker committed
150
        try {
151
            String originalEmail= this.email;
Matt Tucker's avatar
Matt Tucker committed
152 153
            UserManager.getUserProvider().setEmail(username, email);
            this.email = email;
154 155 156 157 158 159
            // Fire event.
            Map params = new HashMap();
            params.put("type", "emailModified");
            params.put("originalValue", originalEmail);
            UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
                    params);
Matt Tucker's avatar
Matt Tucker committed
160 161 162 163 164 165 166 167 168 169 170
        }
        catch (UserNotFoundException unfe) {
            Log.error(unfe);
        }
    }

    public Date getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(Date creationDate) {
171 172 173 174
        if (UserManager.getUserProvider().isReadOnly()) {
            throw new UnsupportedOperationException("User provider is read-only.");
        }

Matt Tucker's avatar
Matt Tucker committed
175
        try {
176
            Date originalCreationDate = this.creationDate;
Matt Tucker's avatar
Matt Tucker committed
177 178
            UserManager.getUserProvider().setCreationDate(username, creationDate);
            this.creationDate = creationDate;
179 180 181 182 183 184 185

            // Fire event.
            Map params = new HashMap();
            params.put("type", "creationDateModified");
            params.put("originalValue", originalCreationDate);
            UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
                    params);
Matt Tucker's avatar
Matt Tucker committed
186 187 188 189 190 191 192 193 194 195 196
        }
        catch (UserNotFoundException unfe) {
            Log.error(unfe);
        }
    }

    public Date getModificationDate() {
        return modificationDate;
    }

    public void setModificationDate(Date modificationDate) {
197 198 199 200
        if (UserManager.getUserProvider().isReadOnly()) {
            throw new UnsupportedOperationException("User provider is read-only.");
        }

Matt Tucker's avatar
Matt Tucker committed
201
        try {
202
            Date originalModificationDate = this.modificationDate;
Matt Tucker's avatar
Matt Tucker committed
203 204
            UserManager.getUserProvider().setCreationDate(username, modificationDate);
            this.modificationDate = modificationDate;
205 206 207 208 209 210 211

            // Fire event.
            Map params = new HashMap();
            params.put("type", "nameModified");
            params.put("originalValue", originalModificationDate);
            UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
                    params);
Matt Tucker's avatar
Matt Tucker committed
212 213 214 215 216
        }
        catch (UserNotFoundException unfe) {
            Log.error(unfe);
        }
    }
Matt Tucker's avatar
Matt Tucker committed
217 218

    /**
219 220
     * Returns all extended properties of the group. Groups
     * have an arbitrary number of extended properties.
Matt Tucker's avatar
Matt Tucker committed
221
     *
222
     * @return the extended properties.
Matt Tucker's avatar
Matt Tucker committed
223
     */
224 225 226 227 228
    public Map<String,String> getProperties() {
        synchronized (this) {
            if (properties == null) {
                properties = new ConcurrentHashMap<String, String>();
                loadProperties();
Matt Tucker's avatar
Matt Tucker committed
229 230
            }
        }
231 232
        // Return a wrapper that will intercept add and remove commands.
        return new PropertiesMap();
Matt Tucker's avatar
Matt Tucker committed
233
    }
Matt Tucker's avatar
Matt Tucker committed
234 235 236 237 238 239 240

    /**
     * Returns the user's roster. A roster is a list of users that the user wishes to know
     * if they are online. Rosters are similar to buddy groups in popular IM clients.
     *
     * @return the user's roster.
     */
241
    public Roster getRoster() {
Matt Tucker's avatar
Matt Tucker committed
242
        try {
243
            return XMPPServer.getInstance().getRosterManager().getRoster(username);
Matt Tucker's avatar
Matt Tucker committed
244 245 246 247 248 249
        }
        catch (UserNotFoundException unfe) {
            Log.error(unfe);
            return null;
        }
    }
Matt Tucker's avatar
Matt Tucker committed
250

Matt Tucker's avatar
Matt Tucker committed
251 252 253 254 255 256 257
    public int getCachedSize() {
        // Approximate the size of the object in bytes by calculating the size
        // of each field.
        int size = 0;
        size += CacheSizes.sizeOfObject();              // overhead of object
        size += CacheSizes.sizeOfLong();                // id
        size += CacheSizes.sizeOfString(username);      // username
258
        size += CacheSizes.sizeOfString(name);          // name
Matt Tucker's avatar
Matt Tucker committed
259 260 261 262 263
        size += CacheSizes.sizeOfString(email);         // email
        size += CacheSizes.sizeOfDate() * 2;            // creationDate and modificationDate
        size += CacheSizes.sizeOfMap(properties);       // properties
        return size;
    }
Matt Tucker's avatar
Matt Tucker committed
264

Matt Tucker's avatar
Matt Tucker committed
265 266 267
    public String toString() {
        return username;
    }
Matt Tucker's avatar
Matt Tucker committed
268

Matt Tucker's avatar
Matt Tucker committed
269 270 271
    public int hashCode() {
        return username.hashCode();
    }
Matt Tucker's avatar
Matt Tucker committed
272

Matt Tucker's avatar
Matt Tucker committed
273 274 275 276 277 278 279 280 281 282 283
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object != null && object instanceof User) {
            return username.equals(((User)object).getUsername());
        }
        else {
            return false;
        }
    }
Matt Tucker's avatar
Matt Tucker committed
284

285 286 287 288 289 290
    /**
     * Map implementation that updates the database when properties are modified.
     */
    private class PropertiesMap extends AbstractMap {

        public Object put(Object key, Object value) {
291
            Map eventParams = new HashMap();
292
            Object answer;
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
            String keyString = (String) key;
            synchronized (keyString.intern()) {
                if (properties.containsKey(key)) {
                    String originalValue = properties.get(key);
                    answer = properties.put(keyString, (String)value);
                    updateProperty(keyString, (String)value);
                    // Configure event.
                    eventParams.put("type", "propertyModified");
                    eventParams.put("propertyKey", key);
                    eventParams.put("originalValue", originalValue);
                }
                else {
                    answer = properties.put(keyString, (String)value);
                    insertProperty(keyString, (String)value);
                    // Configure event.
                    eventParams.put("type", "propertyAdded");
                    eventParams.put("propertyKey", key);
                }
311
            }
312 313 314
            // Fire event.
            UserEventDispatcher.dispatchEvent(User.this,
                    UserEventDispatcher.EventType.user_modified, eventParams);
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
            return answer;
        }

        public Set<Entry> entrySet() {
            return new PropertiesEntrySet();
        }
    }

    /**
     * Set implementation that updates the database when properties are deleted.
     */
    private class PropertiesEntrySet extends AbstractSet {

        public int size() {
            return properties.entrySet().size();
        }

        public Iterator iterator() {
            return new Iterator() {

                Iterator iter = properties.entrySet().iterator();
                Map.Entry current = null;

                public boolean hasNext() {
                    return iter.hasNext();
                }

                public Object next() {
                    current = (Map.Entry)iter.next();
                    return current;
                }

                public void remove() {
                    if (current == null) {
                        throw new IllegalStateException();
                    }
351 352
                    String key = (String)current.getKey();
                    deleteProperty(key);
353 354 355 356
                    iter.remove();
                    // Fire event.
                    Map params = new HashMap();
                    params.put("type", "propertyDeleted");
357
                    params.put("propertyKey", key);
358 359 360 361 362 363 364 365
                    UserEventDispatcher.dispatchEvent(User.this,
                        UserEventDispatcher.EventType.user_modified, params);
                }
            };
        }
    }

    private void loadProperties() {
Matt Tucker's avatar
Matt Tucker committed
366 367 368 369 370
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(LOAD_PROPERTIES);
371
            pstmt.setString(1, username);
Matt Tucker's avatar
Matt Tucker committed
372 373 374 375
            ResultSet rs = pstmt.executeQuery();
            while (rs.next()) {
                properties.put(rs.getString(1), rs.getString(2));
            }
376
            rs.close();
Matt Tucker's avatar
Matt Tucker committed
377
        }
378 379
        catch (SQLException sqle) {
            Log.error(sqle);
Matt Tucker's avatar
Matt Tucker committed
380 381
        }
        finally {
382
            try { if (pstmt != null) pstmt.close(); }
Matt Tucker's avatar
Matt Tucker committed
383
            catch (Exception e) { Log.error(e); }
384
            try { if (con != null) con.close(); }
Matt Tucker's avatar
Matt Tucker committed
385 386 387
            catch (Exception e) { Log.error(e); }
        }
    }
Matt Tucker's avatar
Matt Tucker committed
388

389
    private void insertProperty(String propName, String propValue) {
Matt Tucker's avatar
Matt Tucker committed
390 391 392 393 394
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(INSERT_PROPERTY);
395
            pstmt.setString(1, username);
396 397
            pstmt.setString(2, propName);
            pstmt.setString(3, propValue);
Matt Tucker's avatar
Matt Tucker committed
398 399 400 401 402 403
            pstmt.executeUpdate();
        }
        catch (SQLException e) {
            Log.error(e);
        }
        finally {
404
            try { if (pstmt != null) pstmt.close(); }
Matt Tucker's avatar
Matt Tucker committed
405
            catch (Exception e) { Log.error(e); }
406
            try { if (con != null) con.close(); }
Matt Tucker's avatar
Matt Tucker committed
407 408 409 410
            catch (Exception e) { Log.error(e); }
        }
    }

411
    private void updateProperty(String propName, String propValue) {
Matt Tucker's avatar
Matt Tucker committed
412 413 414 415 416
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(UPDATE_PROPERTY);
417 418
            pstmt.setString(1, propValue);
            pstmt.setString(2, propName);
419
            pstmt.setString(3, username);
Matt Tucker's avatar
Matt Tucker committed
420 421 422 423 424 425
            pstmt.executeUpdate();
        }
        catch (SQLException e) {
            Log.error(e);
        }
        finally {
426
            try { if (pstmt != null) pstmt.close(); }
Matt Tucker's avatar
Matt Tucker committed
427
            catch (Exception e) { Log.error(e); }
428
            try { if (con != null) con.close(); }
Matt Tucker's avatar
Matt Tucker committed
429 430 431 432
            catch (Exception e) { Log.error(e); }
        }
    }

433
    private void deleteProperty(String propName) {
Matt Tucker's avatar
Matt Tucker committed
434 435 436 437 438
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(DELETE_PROPERTY);
439
            pstmt.setString(1, username);
440
            pstmt.setString(2, propName);
Matt Tucker's avatar
Matt Tucker committed
441 442 443 444 445 446
            pstmt.executeUpdate();
        }
        catch (SQLException e) {
            Log.error(e);
        }
        finally {
447
            try { if (pstmt != null) pstmt.close(); }
Matt Tucker's avatar
Matt Tucker committed
448
            catch (Exception e) { Log.error(e); }
449
            try { if (con != null) con.close(); }
Matt Tucker's avatar
Matt Tucker committed
450 451 452
            catch (Exception e) { Log.error(e); }
        }
    }
453
}