Source code: com/RuntimeCollective/webapps/bean/UserGroup.java
1 /* $Header: /home/CVS/rjp/src/com/RuntimeCollective/webapps/bean/UserGroup.java,v 1.34 2003/10/13 15:42:43 fabrice Exp $
2 * $Revision: 1.34 $
3 * $Date: 2003/10/13 15:42:43 $
4 *
5 * ====================================================================
6 *
7 * Josephine : http://www.runtime-collective.com/josephine/index.html
8 *
9 * Copyright (C) 2003 Runtime Collective
10 *
11 * This product includes software developed by the
12 * Apache Software Foundation (http://www.apache.org/).
13 *
14 * This library is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2.1 of the License, or (at your option) any later version.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 *
28 */
29
30 package com.RuntimeCollective.webapps.bean;
31
32 import com.RuntimeCollective.webapps.BeanComparator;
33 import com.RuntimeCollective.webapps.EntityBeanStore;
34 import com.RuntimeCollective.webapps.RuntimeDataSource;
35 import com.RuntimeCollective.webapps.RuntimeParameters;
36 import com.RuntimeCollective.webapps.SearchResults;
37 import com.RuntimeCollective.webapps.WebappsException;
38 import com.RuntimeCollective.webapps.bean.EntityBean;
39
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.HashSet;
45 import java.util.Iterator;
46 import java.util.Vector;
47
48 import java.sql.SQLException;
49
50 /**
51 * A group of users.
52 *
53 * @see com.RuntimeCollective.webapps.form.UserGroupForm
54 *
55 * @version $Id: UserGroup.java,v 1.34 2003/10/13 15:42:43 fabrice Exp $
56 */
57 public class UserGroup implements EntityBean, Collection {
58
59
60 /**
61 * The name of a table (in the default database) that indexes this bean type.
62 * This table must have a PK column called "id" and a row for each of the beans of this type.
63 */
64 public static final String DATABASE_TABLE = "user_group";
65
66 /** The table that maps users to groups. */
67 public static final String MEMBERSHIP_TABLE = "user_group_map";
68
69 private static final String SELECT_ID = "select id from ";
70 private static final String DELETE_FROM = "delete from ";
71 private static final String SELECT_DATA = "select t.name, t.type_id from ";
72 private static final String WHERE_ID = " where id = ";
73 private static final String WHERE_T_ID = " t where t.id = ";
74 private static final String INSERT_INTO = "insert into ";
75 private static final String DATA_VALUES = " (group_id,user_id) values (";
76 private static final String WHERE_GROUP_ID = " where group_id = ";
77 private static final String AND_USER_ID = " and user_id = ";
78 private static final String SELECT_USER_ID = "select user_id from ";
79 private static final String SELECT_ID_NAME = "select id, name from ";
80 private static final String FIRST_NAME = "firstName";
81 private static final String NAME = "name";
82 private static final String TYPE_ID = "type_id";
83 private static final String WHERE_TYPE_ID = " where type_id = ";
84 private static final String WHERE_NAME = " where name = '";
85
86 private static final String AND = " and ";
87 private static final String LAST_USED = " - last_used_date < 60 * ";
88 private static final String SPACE = " ";
89 private static final String SPACE_OPEN = " (";
90 private static final String CLOSE = ")";
91 private static final String ESC = "'";
92 private static final String COMMA = ",";
93 private static final String END_PAR = ")";
94
95
96 // == Instance variables ===================================================
97
98 /** Unique identifier */
99 private int iId;
100
101 /** The name of this user group. */
102 private String iName = "";
103
104 /** The type of this user group. */
105 private int iTypeId = EntityBean.NULL_ID;
106
107 /** A list of Users to add to this UserGroup, when next saved. */
108 private List addBuffer;
109
110 /** A list of Users to remove from this UserGroup, when next saved. */
111 private List removeBuffer;
112
113
114
115 // == Constructors ===================================================
116
117
118 /** Default constructor generates a new blank bean with a new unique ID.
119 */
120 public UserGroup() {
121 try {
122 setId( RuntimeDataSource.nextId() );
123 addBuffer = Collections.synchronizedList(new ArrayList());
124 removeBuffer = Collections.synchronizedList(new ArrayList());
125 if (!RuntimeParameters.isClustered()) {
126 cachedUserIds = Collections.synchronizedList(new ArrayList());
127 }
128 } catch( Exception e ) {
129 RuntimeParameters.logError( this, "Problem getting new id", e );
130 throw new WebappsException( "Problem getting new id "+e );
131 }
132 }
133
134
135 /** Generate a bean from the database for the given primary key.
136 * @param id The primary key of the bean to find.
137 */
138 public UserGroup( int id ) {
139
140 EntityBeanStore store = RuntimeParameters.getStore();
141
142 try {
143
144 // Construct the group
145 Object[] results = RuntimeDataSource.queryRow(SELECT_DATA+DATABASE_TABLE+WHERE_T_ID+id );
146 if (results.length == 2) {
147 setId( id );
148 if (results[0] != null) {
149 setName( results[0].toString() );
150 }
151 if ( results[1]!=null ) {
152 setType( (UserGroupType) store.get(UserGroupType.class.getName(), Integer.parseInt( results[1].toString() ) ) );
153 }
154 } else {
155 RuntimeParameters.logError( this, "no user group found for id = "+id );
156 throw new WebappsException( "no user group found for id = "+id );
157 }
158
159 // Make the add/remove buffers
160 addBuffer = Collections.synchronizedList(new ArrayList());
161 removeBuffer = Collections.synchronizedList(new ArrayList());
162
163 // Populate the cache of user ids
164 if (!RuntimeParameters.isClustered()) {
165 int[] ids = RuntimeDataSource.queryInts((new StringBuffer(60)).append(SELECT_USER_ID).append(MEMBERSHIP_TABLE).append(WHERE_GROUP_ID).append(getId()).toString());
166 cachedUserIds = Collections.synchronizedList(new ArrayList(ids.length+10));
167 for (int i=0; i<ids.length; i++) {
168 cachedUserIds.add(new Integer(ids[i]));
169 }
170 }
171
172 } catch ( Exception e ) {
173 e.printStackTrace();
174 RuntimeParameters.logError( this, "problem getting user group with id = "+id, e );
175 throw new WebappsException( e );
176 }
177 }
178
179
180
181 // == Bean property methods ===================================================
182
183 /** Get unique identifier */
184 public int getId() { return iId; }
185
186
187 /** Set unique identifier */
188 public void setId(int id) { iId = id; }
189
190
191 /** Get the name of this user group. */
192 public String getName() { return iName; }
193
194
195 /** Set the name of this user group. */
196 public void setName(String name) { iName = name; }
197
198
199 /** Get the type of this user group. (optional) */
200 public UserGroupType getType() {
201 if (iTypeId == EntityBean.NULL_ID) {
202 return null;
203 } else {
204 return (UserGroupType) RuntimeParameters.getStore().get(UserGroupType.class.getName(), iTypeId);
205 }
206 }
207
208
209 /** Set the type of this user group. (optional) */
210 public void setType(UserGroupType type) {
211 if (type == null) {
212 iTypeId = EntityBean.NULL_ID;
213 } else {
214 iTypeId = type.getId();
215 }
216 }
217
218
219 /** Returns an iterator over the users in this group. */
220 public Iterator getUsers() {
221 return iterator();
222 }
223
224 /** Get the users as an alphabetically-ordered list of users. */
225 public List getUsersSortedList() {
226 // sort the UserGroup (a collection) by cloning it into a List
227 List sortedUserGroup = Collections.synchronizedList(new ArrayList());
228 synchronized(sortedUserGroup) {
229 Iterator it = iterator();
230 while (it.hasNext())
231 sortedUserGroup.add(it.next());
232 }
233 Collections.sort(sortedUserGroup, new BeanComparator(FIRST_NAME, true, true));
234 return sortedUserGroup;
235 }
236
237
238 // == Entity Bean methods ===========================================
239
240 /** A cache of the user ids saved to the db. */
241 protected List cachedUserIds;
242
243 /** Save this bean to the database. */
244 public void save() {
245 try {
246
247 // clear the cache for this group, if not clustered
248 if (!RuntimeParameters.isClustered()) {
249 RuntimeParameters.getUserGroups().clearFromGroupNameCache(getId());
250 }
251
252 // Save basic group details
253 RuntimeDataSource.save( getId(), DATABASE_TABLE,
254 new String[] { NAME, TYPE_ID },
255 new Object[] { getName(), getType() }
256 );
257
258 Vector sqls = new Vector();
259
260 // Add all users to be added, then wipe the buffer (and update the cache)
261 synchronized(addBuffer) {
262 Iterator i = addBuffer.iterator();
263 User user;
264 while (i.hasNext()) {
265 user = userFromId(i.next());
266 sqls.add(INSERT_INTO+MEMBERSHIP_TABLE+DATA_VALUES+getId()+COMMA+user.getId()+END_PAR);
267 if (!RuntimeParameters.isClustered()) {
268 cachedUserIds.add(new Integer(user.getId()));
269 }
270 }
271 addBuffer.clear();
272 }
273
274 // Remove all users to be removed, then wipe the buffer (and update the cache)
275 synchronized(removeBuffer) {
276 Iterator i = removeBuffer.iterator();
277 User user;
278 while (i.hasNext()) {
279 user = userFromId(i.next());
280 sqls.add(DELETE_FROM+MEMBERSHIP_TABLE+WHERE_GROUP_ID+getId()+AND_USER_ID+user.getId() );
281 if (!RuntimeParameters.isClustered()) {
282 cachedUserIds.remove(new Integer(user.getId()));
283 }
284 }
285 removeBuffer.clear();
286 }
287
288 RuntimeDataSource.update( sqls );
289
290 } catch (Exception e) {
291 RuntimeParameters.logError( this, "problem saving user group id="+getId(), e );
292 throw new WebappsException( "problem saving user group id="+getId() );
293 }
294 }
295
296
297 /** Delete this bean from the database. */
298 public void delete() {
299 try {
300
301 // clear the cache for this group, if not clustered
302 if (!RuntimeParameters.isClustered()) {
303 RuntimeParameters.getUserGroups().clearFromGroupNameCache(getId());
304 }
305
306 // Delete all users and group details
307 // NB the delete cascade trigger should handle this; but can't hurt to do it seperately
308 RuntimeDataSource.update( new String[] {
309 DELETE_FROM+MEMBERSHIP_TABLE+WHERE_GROUP_ID+getId(),
310 DELETE_FROM+DATABASE_TABLE+WHERE_ID+getId() } );
311
312 } catch (Exception e) {
313 RuntimeParameters.logError( this, "problem deleting user group id="+getId(), e );
314 throw new WebappsException( "problem deleting user group id="+getId() );
315 }
316 }
317
318
319 // == Collection methods =============================================
320
321
322 /** Add a user to this group.
323 * <p>(The user will not actually be added until the UserGroup is saved.)
324 * @exception WebappsException thrown if o is not a user.
325 */
326 public boolean add(Object o) {
327 try {
328 Integer userId = idFromUser(o);
329
330 // Remove from the "remove" buffer, if present
331 boolean removeChanged = removeBuffer.remove( userId );
332
333 // Add to the "add" buffer
334 boolean addChanged = !addBuffer.add( userId );
335
336 // Have we changed the add/remove buffers?
337 boolean changed = addChanged || removeChanged;
338
339 // if we weren't adding/removing this user already,
340 // check the existing user group to see if this
341 // action will change it
342 if (addChanged && !removeChanged) {
343 changed = !contains(o);
344 }
345
346 return changed;
347
348 } catch (ClassCastException e) {
349 RuntimeParameters.logError( this, "Trying to add non user object "+o+" to UserGroup", e );
350 throw new WebappsException( e );
351 }
352 }
353
354
355 /** Add a collection of users to this group.
356 * @exception WebappsException thrown if c contains an object that is not a user.
357 */
358 public boolean addAll(Collection c) {
359
360 Iterator it = c.iterator();
361 Object o;
362
363 boolean modified = false;
364
365 // check types of objects being added
366 while ( it.hasNext() ) {
367 o = it.next();
368 try {
369 modified = modified || add( o );
370 } catch (ClassCastException e) {
371 RuntimeParameters.logError( this, "Trying to add non user object "+o+" to UserGroup" );
372 throw new WebappsException( "Trying to add non user object "+o+" to UserGroup" );
373 }
374 }
375
376 return modified;
377 }
378
379
380 /** Remove all users from this group. */
381 public void clear() {
382 // removeAll(getAllUsers());
383
384 // FR: this shoud be much quicker, and doesn't load the users
385 addBuffer.clear();
386 if (!RuntimeParameters.isClustered()) {
387 cachedUserIds.clear();
388 }
389 removeBuffer.addAll(getAllUserIds());
390 }
391
392
393 /** Returns true if the specified user is in this group
394 */
395 public boolean contains(Object o) {
396 // return getAllUsers().contains(o);
397
398 // FR: this doesn't load the users
399 if (o == null) {
400 return false;
401 }
402 return getAllUserIds().contains(new Integer(((User) o).getId()));
403 }
404
405
406 /** Returns true if this group contains a collection of users. */
407 public boolean containsAll(Collection c) {
408 // return getAllUsers().containsAll( c );
409
410 // FR: bit more complicated, but doesn't load the users
411 if (c == null) {
412 return true;
413 }
414 List ids = new ArrayList(c.size());
415 for (Iterator it = c.iterator(); it.hasNext(); ) {
416 ids.add(new Integer(((User) it.next()).getId()));
417 }
418 return getAllUserIds().containsAll(ids);
419 }
420
421
422 /** Compares the specified object with this group for equality. */
423 /*
424 public boolean equals(Object o) {
425 return iUsers.equals( idFromUser(o) );
426 }
427 */
428
429 /** Returns the hash code value for this group. */
430 public int hashCode() {
431 return getAllUsers().hashCode();
432 }
433
434
435 /** Returns true if this group contains no users. */
436 public boolean isEmpty() {
437 // return getAllUsers().isEmpty();
438
439 // FR: this doesn't load the users
440 return getAllUserIds().isEmpty();
441 }
442
443
444 /** Returns an iterator over the users in this group. */
445 public Iterator iterator() {
446 return getAllUsers().iterator();
447 }
448
449
450 /** Removes a single instance of the specified user from this group, if it is present. */
451 public boolean remove(Object o) {
452 try {
453 Integer userId = idFromUser(o);
454
455 // Remove from the "add" buffer, if present
456 boolean addChanged = addBuffer.remove( userId );
457
458 // Add to the "remove" buffer
459 boolean removeChanged = !removeBuffer.add( userId );
460
461 // Have we changed the add/remove buffers?
462 boolean changed = removeChanged || addChanged;
463
464 // if we weren't adding/removing this user already,
465 // check the existing user group to see if this
466 // action will change it
467 if (removeChanged && !addChanged) {
468 changed = contains(o);
469 }
470
471 return changed;
472
473 } catch (ClassCastException e) {
474 RuntimeParameters.logError( this, "Trying to remove non user object "+o+" to UserGroup", e );
475 throw new WebappsException( e );
476 }
477 }
478
479
480 /** Removes all this group's users that are also contained in the specified collection. */
481 public boolean removeAll(Collection c) {
482 Iterator it = c.iterator();
483 Object o;
484
485 boolean modified = false;
486
487 // check types of objects being added
488 while ( it.hasNext() ) {
489 o = it.next();
490 try {
491 modified = modified || remove( o );
492 } catch (ClassCastException e) {
493 RuntimeParameters.logError( this, "Trying to remove non user object "+o+" from UserGroup" );
494 throw new WebappsException( "Trying to remove non user object "+o+" from UserGroup" );
495 }
496 }
497
498 return modified;
499 }
500
501
502 /** Retains only the users in this group that are contained in the specified collection. */
503 public boolean retainAll(Collection c) {
504 boolean modified = false;
505 Iterator i = iterator();
506 Object o;
507
508 while (i.hasNext()) {
509 o = i.next();
510 if (!c.contains(o)) {
511 modified = modified || remove( o );
512 }
513 }
514
515 return modified;
516 }
517
518
519 /** Returns the number of users in this group. */
520 public int size() {
521 // return getAllUsers().size();
522
523 // FR: this doesn't load the users
524 return getAllUserIds().size();
525 }
526
527
528 /** Returns an array of Users in this group
529 * @return an array of Users
530 */
531 public Object[] toArray() {
532 return getAllUsers().toArray( new User[] {} );
533 }
534
535
536 /** Returns an array of Users in this group, using the specified array, if possible.
537 *
538 * @param a the array into which the elements of the list are to
539 * be stored, if it is big enough; otherwise, a new array of the
540 * same runtime type is allocated for this purpose.
541 * @return an array containing the elements of the list.
542 */
543 public Object[] toArray( Object[] a ) {
544 return getAllUsers().toArray(a);
545 }
546
547
548
549
550 // == Private methods ==================================================
551
552 /**
553 * Get all users in this usergroup, by getting
554 * the list of ids and loading all the users from the EBS.
555 */
556 private Collection getAllUsers() {
557 return usersFromIds(getAllUserIds());
558 }
559
560
561 /**
562 * Get the ids of all users in this usergroup.
563 * If clustered, this queries the the database every time.
564 * Otherwise we use the cache.
565 * It takes into account users in the add/remove buffers
566 * that have not been saved yet.
567 */
568 private Collection getAllUserIds() {
569 try {
570 ArrayList iUsers = null;
571
572 if (RuntimeParameters.isClustered()) {
573 // clustered - don't use the cache
574
575 // Get all the user ids in this group
576 int[] ids = RuntimeDataSource.queryInts((new StringBuffer(60)).append(SELECT_USER_ID).append(MEMBERSHIP_TABLE).append(WHERE_GROUP_ID).append(getId()).toString());
577 iUsers = new ArrayList(ids.length+10);
578 for (int i=0; i<ids.length; i++) {
579 iUsers.add(new Integer(ids[i]));
580 }
581
582 } else {
583 iUsers = new ArrayList(cachedUserIds.size()+10);
584 iUsers.addAll(cachedUserIds);
585 }
586
587 // add those in the "add" buffer
588 synchronized(addBuffer) {
589 Iterator i = addBuffer.iterator();
590 while (i.hasNext()) {
591 iUsers.add(i.next());
592 }
593 }
594
595 // remove those in the "remove" buffer
596 // (a User should not be in both buffers)
597 synchronized(removeBuffer) {
598 Iterator i = removeBuffer.iterator();
599 while (i.hasNext()) {
600 iUsers.remove(i.next());
601 }
602 }
603
604 return iUsers;
605
606 } catch (Exception e) {
607 RuntimeParameters.logError(this, "UserGroup: Problem fetching user ids from db: ", e);
608 throw new WebappsException("UserGroup: Problem fetching user ids from db: "+e);
609 }
610 }
611
612
613 // == Static methods ==================================================
614 // NOTE: all these methods have been deprecated, and moved to webapps.UserGroups
615
616 /** Get a list of all the user groups.
617 * @deprecated Instead, use <code>RuntimeParameters.getUserGroups().getAllGroups()</code>
618 * @see com.RuntimeCollective.webapps.UserGroups#getAllGroups()
619 */
620 public static SearchResults getAllGroups() {
621 // NB we return a SearchResults with a description of each group, rather than an iterator of them, so that we avoid instantiating all groups (and potentially all users) wherever possible.
622 try {
623 return RuntimeDataSource.search(SELECT_ID_NAME+DATABASE_TABLE );
624 } catch (Exception e) {
625 RuntimeParameters.logError(UserGroup.class.getName(), "Problem finding groups", e);
626 throw new WebappsException(e);
627 }
628 }
629
630
631 /** Get a list of all the user groups of the given type.
632 * @deprecated Instead, use <code>RuntimeParameters.getUserGroups().getAllGroups(UserGroupType type)</code>
633 * @see com.RuntimeCollective.webapps.UserGroups#getAllGroups(com.RuntimeCollective.webapps.bean.UserGroupType)
634 */
635 public static SearchResults getAllGroups( UserGroupType type ) {
636 // NB we return a SearchResults with a description of each group, rather than an iterator of them, so that we avoid instantiating all groups (and potentially all users) wherever possible.
637 try {
638 return RuntimeDataSource.search(SELECT_ID_NAME+DATABASE_TABLE+WHERE_TYPE_ID+type.getId() );
639 } catch (Exception e) {
640 RuntimeParameters.logError(UserGroup.class.getName(), "Problem finding groups", e );
641 throw new WebappsException(e);
642 }
643 }
644
645
646 /** Get a user group for the given name.
647 * @deprecated Instead, use <code>RuntimeParameters.getUserGroups().getForName(String name)</code>
648 * @see com.RuntimeCollective.webapps.UserGroups#getForName(java.lang.String)
649 * @return The user group with the given name, or null if none exists
650 */
651 public static UserGroup getForName( String name ) {
652 try {
653 int[] id = RuntimeDataSource.queryInts(SELECT_ID+DATABASE_TABLE+WHERE_NAME+RuntimeDataSource.escape(name)+ESC);
654 if ( id.length==0 ) return null;
655 else return (UserGroup) RuntimeParameters.getStore().get(UserGroup.class.getName(), id[0] );
656 } catch (SQLException e) {
657 RuntimeParameters.logError(UserGroup.class.getName(), "Problem finding groups for name="+name, e );
658 throw new WebappsException(e);
659 }
660 }
661
662 /**
663 * Returns the ID of a User object, if indeed this object is a User
664 * @exception ClassCastException if <code>o</code> is not a User
665 */
666 protected Integer idFromUser(Object o) throws ClassCastException {
667 return new Integer( ((User) o).getId());
668 }
669
670 /**
671 * Returns a User from an Integer object , if indeed this object is an Integer
672 * @exception ClassCastException if <code>o</code> is not a Integer
673 */
674 protected User userFromId(Object o) throws ClassCastException {
675 return (User)RuntimeParameters.getStore().get(User.class.getName(), ((Integer)o).intValue());
676 }
677
678 /**
679 * Takes a Collection of Users, and returns
680 * a Collection of their ids, as Integers.
681 */
682 protected Collection idsFromUsers(Collection c) {
683 ArrayList a = new ArrayList();
684 Iterator it = c.iterator();
685 while (it.hasNext()) {
686 a.add( idFromUser(it.next()) );
687 }
688 return a;
689 }
690
691 /**
692 * Takes a Collection of Integer ids, and returns
693 * a Collection of the corresponding Users.
694 */
695 protected Collection usersFromIds(Collection c) {
696 ArrayList a = new ArrayList();
697 Iterator it = c.iterator();
698 while (it.hasNext()) {
699 int i = ((Integer) it.next()).intValue();
700 a.add(RuntimeParameters.getStore().get(User.class.getName(), i));
701 }
702 return a;
703 }
704 }
705
706