1   /*
2    *  AccessControllerImpl.java
3    *
4    *  Copyright (c) 1998-2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Marin Dimitrov, 19/Sep/2001
12   *
13   *  $Id: AccessControllerImpl.java,v 1.46 2002/03/13 14:52:11 marin Exp $
14   */
15  
16  package gate.security;
17  
18  import java.util.*;
19  import java.sql.*;
20  import java.net.*;
21  
22  import junit.framework.*;
23  
24  import gate.*;
25  import gate.event.*;
26  import gate.persist.*;
27  import gate.util.MethodNotImplementedException;
28  
29  
30  public class AccessControllerImpl
31    implements AccessController, ObjectModificationListener {
32  
33    public static final int DEFAULT_SESSION_TIMEOUT_MIN = 4*60;
34  
35    public static final int LOGIN_OK = 1;
36    public static final int LOGIN_FAILED = 2;
37  
38    private static long MY_VERY_SECRET_CONSTANT;
39    private static final int RANDOM_MAX = 1024;
40  
41    private HashMap     sessions;
42    private HashMap     sessionLastUsed;
43    private HashMap     sessionTimeouts;
44  
45    private Connection  jdbcConn;
46    private String      jdbcURL;
47  
48    private HashMap     usersByID;
49    private HashMap     usersByName;
50  
51    private HashMap     groupsByID;
52    private HashMap     groupsByName;
53  
54    private static Random r;
55    private boolean isPooled;
56  
57    private int refCnt;
58  
59    /** --- */
60    private Vector omModificationListeners;
61    /** --- */
62    private Vector omCreationListeners;
63    /** --- */
64    private Vector omDeletionListeners;
65  
66  
67    static {
68      r = new Random();
69      MY_VERY_SECRET_CONSTANT = r.nextInt(RANDOM_MAX) * r.nextInt(RANDOM_MAX)
70                                    + Math.round(Math.PI * Math.E);
71    }
72  
73    /** --- */
74    public AccessControllerImpl(String jdbcURL) {
75  
76      Assert.assertNotNull(jdbcURL);
77  
78      this.refCnt = 0;
79      this.jdbcURL = jdbcURL;
80  
81      sessions = new HashMap();
82      sessionLastUsed = new HashMap();
83      sessionTimeouts = new HashMap();
84  
85      usersByID =  new HashMap();
86      usersByName=  new HashMap();
87  
88      groupsByID = new HashMap();
89      groupsByName = new HashMap();
90  
91      this.omModificationListeners = new Vector();
92      this.omCreationListeners = new Vector();
93      this.omDeletionListeners = new Vector();
94    }
95  
96    /** --- */
97    public void open()
98      throws PersistenceException{
99  
100     synchronized(this) {
101       if (refCnt++ == 0) {
102         //open connection
103         try {
104           //1. get connection to the database
105           jdbcConn = DBHelper.connect(this.jdbcURL);
106 
107           Assert.assertNotNull(jdbcConn);
108 
109           //2. initialize group/user collections
110           //init, i.e. read users and groups from DB
111           init();
112         }
113         catch(SQLException sqle) {
114           throw new PersistenceException("could not get DB connection ["+ sqle.getMessage() +"]");
115         }
116         catch(ClassNotFoundException clse) {
117           throw new PersistenceException("cannot locate JDBC driver ["+ clse.getMessage() +"]");
118         }
119       }
120     }
121 
122 
123   }
124 
125   /** --- */
126   public void close()
127     throws PersistenceException{
128 
129     if (--this.refCnt == 0) {
130 
131       //0. Invalidate all sessions
132       this.sessions.clear();
133       this.sessionLastUsed.clear();
134       this.sessionTimeouts.clear();
135 
136       //1. deregister self as listener for groups
137       Set groupMappings = this.groupsByName.entrySet();
138       Iterator itGroups = groupMappings.iterator();
139 
140       while (itGroups.hasNext()) {
141         Map.Entry mapEntry = (Map.Entry)itGroups.next();
142         GroupImpl  grp = (GroupImpl)mapEntry.getValue();
143         grp.unregisterObjectModificationListener(this,
144                                                ObjectModificationEvent.OBJECT_MODIFIED);
145       }
146 
147       //1.1. deregister self as listener for users
148       Set userMappings = this.usersByName.entrySet();
149       Iterator itUsers = userMappings.iterator();
150 
151       while (itUsers.hasNext()) {
152         Map.Entry mapEntry = (Map.Entry)itUsers.next();
153         UserImpl  usr = (UserImpl)mapEntry.getValue();
154         usr.unregisterObjectModificationListener(this,
155                                              ObjectModificationEvent.OBJECT_MODIFIED);
156       }
157 
158       //1.2 release all listeners registered for this object
159       this.omCreationListeners.removeAllElements();
160       this.omDeletionListeners.removeAllElements();
161       this.omModificationListeners.removeAllElements();
162 
163       //2. delete all groups/users collections
164       this.groupsByID.clear();
165       this.groupsByName.clear();
166       this.usersByID.clear();
167       this.groupsByName.clear();
168 
169       //3.close connection (if not pooled)
170       try {
171         if (false == this.isPooled) {
172           this.jdbcConn.close();
173         }
174       }
175       catch(SQLException sqle) {
176         throw new PersistenceException("can't close connection to DB:["+
177                                         sqle.getMessage()+"]");
178       }
179     }
180   }
181 
182   /** --- */
183   public Group findGroup(String name)
184     throws PersistenceException,SecurityException{
185 
186     Group grp = (Group)this.groupsByName.get(name);
187 
188     if (null == grp) {
189       throw new SecurityException("No such group");
190     }
191 
192     return grp;
193   }
194 
195   /** --- */
196   public Group findGroup(Long id)
197     throws PersistenceException,SecurityException {
198 
199     Group grp = (Group)this.groupsByID.get(id);
200 
201     if (null == grp) {
202       throw new SecurityException("No such group");
203     }
204 
205     return grp;
206   }
207 
208   /** --- */
209   public User findUser(String name)
210     throws PersistenceException,SecurityException {
211 
212     User usr = (User)this.usersByName.get(name);
213 
214     if (null == usr) {
215       throw new SecurityException("No such user");
216     }
217 
218     return usr;
219   }
220 
221   /** --- */
222   public User findUser(Long id)
223     throws PersistenceException,SecurityException {
224 
225     User usr = (User)this.usersByID.get(id);
226 
227     if (null == usr) {
228       throw new SecurityException("No such user");
229     }
230 
231     return usr;
232   }
233 
234   /** --- */
235   public Session findSession(Long id)
236     throws SecurityException {
237 
238     Session s = (Session)this.sessions.get(id);
239 
240     if (null==s) {
241       throw new SecurityException("No such session ID!");
242     }
243 
244     return s;
245   }
246 
247   /** --- */
248   public Group createGroup(String name,Session s)
249     throws PersistenceException, SecurityException {
250 
251     Assert.assertNotNull(name);
252 
253     //-1. check session
254     if (false == isValidSession(s)) {
255       throw new SecurityException("invalid session supplied");
256     }
257 
258     //0. check privileges
259     if (false == s.isPrivilegedSession()) {
260       throw new SecurityException("insufficient privileges");
261     }
262 
263 
264     //1. create group in DB
265     CallableStatement stmt = null;
266     Long new_id;
267 
268     try {
269       stmt = this.jdbcConn.prepareCall(
270               "{ call "+Gate.DB_OWNER+".security.create_group(?,?)} ");
271       stmt.setString(1,name);
272       //numbers generated from Oracle sequences are BIGINT
273       stmt.registerOutParameter(2,java.sql.Types.BIGINT);
274       stmt.execute();
275       new_id = new Long(stmt.getLong(2));
276     }
277     catch(SQLException sqle) {
278 
279       //check for more specifi exceptions
280       switch(sqle.getErrorCode()) {
281 
282         case DBHelper.X_ORACLE_DUPLICATE_GROUP_NAME:
283           throw new PersistenceException(
284                 "can't create a group in DB, name is not unique: ["
285                   + sqle.getMessage()+"]");
286 
287         default:
288           throw new PersistenceException(
289                 "can't create a group in DB: ["+ sqle.getMessage()+"]");
290       }
291 
292     }
293     finally {
294       DBHelper.cleanup(stmt);
295     }
296 
297     //2. create GroupImpl for the new group and
298     // users list is empty
299     GroupImpl grp = new GroupImpl(new_id,name,new Vector(),this,this.jdbcConn);
300 
301     //3. register as objectModification listener for this group
302     //we care only about name changes
303     grp.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
304 
305     //4.put in collections
306     this.groupsByID.put(new_id,grp);
307     this.groupsByName.put(name,grp);
308 
309     return grp;
310   }
311 
312   /** --- */
313   public void deleteGroup(Long id, Session s)
314     throws PersistenceException,SecurityException {
315 
316     Group grp = (Group)this.groupsByID.get(id);
317     if (null == grp) {
318       throw new SecurityException("incorrect group id supplied ( id = ["+id+"])");
319     }
320 
321     //delegate
322     deleteGroup(grp,s);
323   }
324 
325   /** --- */
326   public void deleteGroup(Group grp, Session s)
327     throws PersistenceException,SecurityException {
328 
329     //1. check session
330     if (false == isValidSession(s)) {
331       throw new SecurityException("invalid session supplied");
332     }
333 
334     //2. check privileges
335     if (false == s.isPrivilegedSession()) {
336       throw new SecurityException("insufficient privileges");
337     }
338 
339     //3. delete in DB
340     CallableStatement stmt = null;
341 
342     try {
343       stmt = this.jdbcConn.prepareCall(
344                 "{ call "+Gate.DB_OWNER+".security.delete_group(?) } ");
345       stmt.setLong(1,grp.getID().longValue());
346       stmt.execute();
347     }
348     catch(SQLException sqle) {
349       //check for more specific exceptions
350       switch(sqle.getErrorCode()) {
351 
352         case DBHelper.X_ORACLE_GROUP_OWNS_RESOURCES:
353           throw new PersistenceException(
354                 "can't delete a group from DB, the group owns LR(s): ["
355                   + sqle.getMessage()+"]");
356 
357         default:
358           throw new PersistenceException(
359                 "can't delete a group from DB: ["+ sqle.getMessage()+"]");
360       }
361     }
362     finally {
363       DBHelper.cleanup(stmt);
364     }
365 
366     //4. delete from collections
367     this.groupsByID.remove(grp.getID());
368     this.groupsByName.remove(grp.getName());
369 
370     //5. notify all other listeners
371     //this one is tricky - sent OBJECT_DELETED event to all who care
372     //but note that the SOURCE is not us but the object being deleted
373     ObjectModificationEvent e = new ObjectModificationEvent(
374                       grp,
375                       ObjectModificationEvent.OBJECT_DELETED,
376                       0);
377 
378     fireObjectDeletedEvent(e);
379 
380     //6. this one is tricky: invalidate all sessions
381     //that are for user logged in as members of this group
382     Set sessionMappings = this.sessions.entrySet();
383     Iterator itSessions = sessionMappings.iterator();
384 
385     //6.1 to avoid ConcurrentModificationException store the sessions
386     //found in a temp vector
387     Vector sessionsToDelete = new Vector();
388     while (itSessions.hasNext()) {
389       Map.Entry mapEntry = (Map.Entry)itSessions.next();
390       SessionImpl  ses = (SessionImpl)mapEntry.getValue();
391       if (ses.getGroup().equals(grp)) {
392         //logout(ses); --> this will cause ConcurrentModificationException
393         sessionsToDelete.add(ses);
394       }
395     }
396     //6.2 now delete sessions
397     for (int i=0; i< sessionsToDelete.size(); i++) {
398       Session ses = (Session)sessionsToDelete.elementAt(i);
399       logout(ses);
400     }
401 
402   }
403 
404   /** --- */
405   public User createUser(String name, String passwd,Session s)
406     throws PersistenceException,SecurityException {
407 
408     Assert.assertNotNull(name);
409 
410     //-1. check session
411     if (false == isValidSession(s)) {
412       throw new SecurityException("invalid session supplied");
413     }
414 
415     //0. check privileges
416     if (false == s.isPrivilegedSession()) {
417       throw new SecurityException("insufficient privileges");
418     }
419 
420     //1. create user in DB
421     CallableStatement stmt = null;
422     Long new_id;
423 
424     try {
425       stmt = this.jdbcConn.prepareCall(
426                 "{ call "+Gate.DB_OWNER+".security.create_user(?,?,?)} ");
427       stmt.setString(1,name);
428       stmt.setString(2,passwd);
429       //numbers generated from Oracle sequences are BIGINT
430       stmt.registerOutParameter(3,java.sql.Types.BIGINT);
431       stmt.execute();
432       new_id = new Long(stmt.getLong(3));
433     }
434     catch(SQLException sqle) {
435       //check for more specific exceptions
436       switch(sqle.getErrorCode()) {
437 
438         case DBHelper.X_ORACLE_DUPLICATE_USER_NAME:
439           throw new PersistenceException(
440                 "can't create a user in DB, name is not unique: ["
441                   + sqle.getMessage()+"]");
442         default:
443           throw new PersistenceException(
444                 "can't create a user in DB: ["+ sqle.getMessage()+"]");
445       }
446     }
447     finally {
448       DBHelper.cleanup(stmt);
449     }
450 
451     //2. create UserImpl for the new user
452     // groups list is empty
453     UserImpl usr = new UserImpl(new_id,name,new Vector(),this,this.jdbcConn);
454 
455     //3. register as objectModification listener for this user
456     //we care only about user changing name
457     usr.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
458 
459     //4. put in collections
460     this.usersByID.put(new_id,usr);
461     this.usersByName.put(name,usr);
462 
463     return usr;
464   }
465 
466   /** --- */
467   public void deleteUser(User usr, Session s)
468     throws PersistenceException,SecurityException {
469 
470     //1. check session
471     if (false == isValidSession(s)) {
472       throw new SecurityException("invalid session supplied");
473     }
474 
475     //2. check privileges
476     if (false == s.isPrivilegedSession()) {
477       throw new SecurityException("insufficient privileges");
478     }
479 
480     //3. delete in DB
481     CallableStatement stmt = null;
482 
483     try {
484       stmt = this.jdbcConn.prepareCall(
485                   "{ call "+Gate.DB_OWNER+".security.delete_user(?) } ");
486       stmt.setLong(1,usr.getID().longValue());
487       stmt.execute();
488     }
489     catch(SQLException sqle) {
490       switch(sqle.getErrorCode()) {
491 
492         case DBHelper.X_ORACLE_USER_OWNS_RESOURCES:
493           throw new PersistenceException(
494                 "can't delete user from DB, the user owns LR(s): ["
495                   + sqle.getMessage()+"]");
496         default:
497           throw new PersistenceException(
498                 "can't delete user from DB: ["+ sqle.getMessage()+"]");
499       }
500     }
501     finally {
502       DBHelper.cleanup(stmt);
503     }
504 
505     //4. delete from collections
506     this.usersByID.remove(usr.getID());
507     this.usersByName.remove(usr.getName());
508 
509     //6. notify all other listeners
510     //this one is tricky - sent OBJECT_DELETED event to all who care
511     //but note that the SOURCE is not us but the object being deleted
512     ObjectModificationEvent e = new ObjectModificationEvent(
513                       usr,
514                       ObjectModificationEvent.OBJECT_DELETED,
515                       0);
516 
517     fireObjectDeletedEvent(e);
518 
519     //7. this one is tricky: invalidate all sessions for the user
520     Set sessionMappings = this.sessions.entrySet();
521     Iterator itSessions = sessionMappings.iterator();
522 
523     //7.1 to avoid ConcurrentModificationException store the sessions
524     //found in a temp vector
525     Vector sessionsToDelete = new Vector();
526     while (itSessions.hasNext()) {
527       Map.Entry mapEntry = (Map.Entry)itSessions.next();
528       SessionImpl  ses = (SessionImpl)mapEntry.getValue();
529       if (ses.getUser().equals(usr)) {
530         //logout(ses); --> this will cause ConcurrentModificationException
531         sessionsToDelete.add(ses);
532       }
533     }
534     //7.2 now delete sessions
535     for (int i=0; i< sessionsToDelete.size(); i++) {
536       Session ses = (Session)sessionsToDelete.elementAt(i);
537       logout(ses);
538     }
539 
540   }
541 
542 
543   /** --- */
544   public void deleteUser(Long id, Session s)
545     throws PersistenceException,SecurityException {
546 
547     User usr = (User)usersByID.get(id);
548     if (null == usr) {
549       throw new SecurityException("incorrect user id supplied ( id = ["+id+"])");
550     }
551 
552     //delegate
553     deleteUser(usr,s);
554   }
555 
556   /** --- */
557   public Session login(String usr_name, String passwd,Long prefGroupID)
558     throws PersistenceException,SecurityException {
559 
560     //1. check the user locally
561     User usr = (User)this.usersByName.get(usr_name);
562     if (null == usr) {
563       throw new SecurityException("no such user (username=["+usr_name+"])");
564     }
565 
566     //2. check group localy
567     Group grp = (Group)this.groupsByID.get(prefGroupID);
568     if (null == grp) {
569       throw new SecurityException("no such group (id=["+prefGroupID+"])");
570     }
571 
572     //2. check user/pass in DB
573     CallableStatement stmt = null;
574     boolean isPrivilegedUser = false;
575 
576     try {
577       stmt = this.jdbcConn.prepareCall(
578                 "{ call "+Gate.DB_OWNER+".security.login(?,?,?,?)} ");
579       stmt.setString(1,usr_name);
580       stmt.setString(2,passwd);
581       stmt.setLong(3,prefGroupID.longValue());
582       stmt.registerOutParameter(4,java.sql.Types.NUMERIC);
583       stmt.execute();
584       isPrivilegedUser = (stmt.getInt(4) == DBHelper.FALSE ? false : true );
585     }
586     catch(SQLException sqle) {
587       switch(sqle.getErrorCode())
588       {
589         case DBHelper.X_ORACLE_INVALID_USER_NAME :
590           throw new SecurityException("Login failed: incorrect user");
591         case DBHelper.X_ORACLE_INVALID_USER_PASS :
592           throw new SecurityException("Login failed: incorrect password");
593         case DBHelper.X_ORACLE_INVALID_USER_GROUP :
594           throw new SecurityException("Login failed: incorrect group");
595         default:
596           throw new PersistenceException("can't login user, DB error is: ["+
597                                           sqle.getMessage()+"]");
598       }
599     }
600     finally {
601       DBHelper.cleanup(stmt);
602     }
603 
604 
605     //3. create a Session and set User/Group
606     Long sessionID = createSessionID();
607     while (this.sessions.containsKey(sessionID)) {
608       sessionID = createSessionID();
609     }
610 
611     SessionImpl s = new SessionImpl(sessionID,
612                                     usr,
613                                     grp,
614                                     DEFAULT_SESSION_TIMEOUT_MIN,
615                                     isPrivilegedUser);
616 
617     //4. add session to session collections
618     this.sessions.put(s.getID(),s);
619 
620     //5. set the session timeouts and keep alives
621     this.sessionTimeouts.put(sessionID,new Long(DEFAULT_SESSION_TIMEOUT_MIN));
622     touchSession(s); //this one changes the keepAlive time
623 
624     return s;
625   }
626 
627   /** --- */
628   public void logout(Session s)
629     throws SecurityException {
630 
631     Assert.assertNotNull(s);
632     Long SID = s.getID();
633 
634     //1.sessions
635     Session removedSession = (Session)this.sessions.remove(SID);
636     Assert.assertNotNull(removedSession);
637 
638     //2. keep alives
639     Object lastUsed = this.sessionLastUsed.remove(SID);
640     Assert.assertNotNull(lastUsed);
641 
642     //3. timeouts
643     Object timeout = this.sessionTimeouts.remove(SID);
644     Assert.assertNotNull(timeout);
645   }
646 
647   /** --- */
648   public void setSessionTimeout(Session s, int timeoutMins)
649     throws SecurityException {
650 
651     this.sessionTimeouts.put(s.getID(),new Long(timeoutMins));
652   }
653 
654   /** --- */
655   public boolean isValidSession(Session s) {
656 
657     //1. do we have such session?
658     if (false == this.sessions.containsKey(s.getID())) {
659       return false;
660     }
661 
662     //2. has it expired meanwhile?
663     Assert.assertNotNull(this.sessionLastUsed.get(s.getID()));
664 
665     long lastUsedMS = ((Long)this.sessionLastUsed.get(s.getID())).longValue();
666     long sessTimeoutMin = ((Long)this.sessionTimeouts.get(s.getID())).longValue();
667     long currTimeMS = System.currentTimeMillis();
668     //timeout is in minutes
669     long lastUsedMin = (currTimeMS-lastUsedMS)/(1000*60);
670 
671     if (lastUsedMin > sessTimeoutMin) {
672       //oops, session expired
673       //invalidate it and fail
674       try {
675         logout(s);
676       }
677       catch(SecurityException se) {
678         //well, this can happen only if logout() was called together
679         //with isValidSesion() but the possibility it too low to care
680         //and synchronize access
681         ;
682       }
683 
684       return false;
685     }
686 
687     //everything ok
688     //touch session
689     touchSession(s);
690 
691     return true;
692   }
693 
694   /** -- */
695   public List listGroups()
696     throws PersistenceException {
697 
698     //1. read all groups from DB
699     Statement stmt = null;
700     ResultSet rs = null;
701     String    sql;
702     Vector    result = new Vector();
703 
704     try {
705       stmt = this.jdbcConn.createStatement();
706 
707       //1.1 read groups
708       sql = " SELECT grp_name "+
709             " FROM   "+Gate.DB_OWNER+".t_group "+
710             " ORDER BY grp_name ASC";
711       rs = stmt.executeQuery(sql);
712 
713       while (rs.next()) {
714         //access by index is faster
715         //first column index is 1
716         String grp_name = rs.getString(1);
717         result.add(grp_name);
718       }
719 
720       return result;
721     }
722     catch (SQLException sqle) {
723       throw new PersistenceException("cannot read groups from DB :["+
724                                         sqle.getMessage() +"]");
725     }
726     finally {
727       DBHelper.cleanup(rs);
728       DBHelper.cleanup(stmt);
729     }
730   }
731 
732   /** -- */
733   public List listUsers()
734     throws PersistenceException {
735 
736     //1. read all users from DB
737     Statement stmt = null;
738     ResultSet rs = null;
739     String    sql;
740     Vector    result = new Vector();
741 
742     try {
743       stmt = this.jdbcConn.createStatement();
744 
745       //1.1 read groups
746       sql = " SELECT usr_login "+
747             " FROM   "+Gate.DB_OWNER+".t_user "+
748             " ORDER BY usr_login DESC";
749       rs = stmt.executeQuery(sql);
750 
751       while (rs.next()) {
752         //access by index is faster
753         //first column index is 1
754         String usr_name = rs.getString(1);
755         result.add(usr_name);
756       }
757 
758       return result;
759     }
760     catch (SQLException sqle) {
761       throw new PersistenceException("cannot read groups from DB :["+
762                                         sqle.getMessage() +"]");
763     }
764     finally {
765       DBHelper.cleanup(rs);
766       DBHelper.cleanup(stmt);
767     }
768   }
769 
770 
771 
772   /*  private methods */
773 
774   private void touchSession(Session s) {
775 
776     this.sessionLastUsed.put(s.getID(),  new Long(System.currentTimeMillis()));
777   }
778 
779 
780   private Long createSessionID() {
781 
782     //need a hint?
783     return new Long(((System.currentTimeMillis() << 16) >> 16)*
784                       (r.nextInt(RANDOM_MAX))*
785                           Runtime.getRuntime().freeMemory()*
786                               MY_VERY_SECRET_CONSTANT);
787   }
788 
789 
790   private boolean canDeleteGroup(Group grp)
791     throws PersistenceException, SecurityException{
792 
793     //1. check group localy
794     if (false == this.groupsByID.containsValue(grp)) {
795       throw new SecurityException("no such group (id=["+grp.getID()+"])");
796     }
797 
798     //2. check DB
799     CallableStatement stmt = null;
800 
801     try {
802       stmt = this.jdbcConn.prepareCall(
803                 "{ ? = call "+Gate.DB_OWNER+".security.can_delete_group(?) }");
804       stmt.registerOutParameter(1,java.sql.Types.INTEGER);
805       stmt.setLong(2,grp.getID().longValue());
806       stmt.execute();
807       boolean res = stmt.getBoolean(1);
808 
809       return res;
810     }
811     catch(SQLException sqle) {
812       throw new PersistenceException("can't perform document checks, DB error is: ["+
813                                           sqle.getMessage()+"]");
814     }
815     finally {
816       DBHelper.cleanup(stmt);
817     }
818 
819   }
820 
821 
822   private boolean canDeleteUser(User usr)
823     throws PersistenceException, SecurityException{
824 
825     //1. check group localy
826     if (false == this.usersByID.containsValue(usr)) {
827       throw new SecurityException("no such user (id=["+usr.getID()+"])");
828     }
829 
830     //2. check DB
831     CallableStatement stmt = null;
832 
833     try {
834       stmt = this.jdbcConn.prepareCall(
835                 "{ ? = call "+Gate.DB_OWNER+".security.can_delete_user(?) }");
836 
837       stmt.registerOutParameter(1,java.sql.Types.INTEGER);
838       stmt.setLong(2,usr.getID().longValue());
839       stmt.execute();
840       boolean res = stmt.getBoolean(1);
841 
842       return res;
843     }
844     catch(SQLException sqle) {
845       throw new PersistenceException("can't perform document checks, DB error is: ["+
846                                           sqle.getMessage()+"]");
847     }
848     finally {
849       DBHelper.cleanup(stmt);
850     }
851 
852   }
853 
854   private void init()
855     throws PersistenceException {
856 
857     //1. read all groups and users from DB
858     Statement stmt = null;
859     ResultSet rs = null;
860     String    sql;
861     Hashtable   groupNames = new Hashtable();
862     Hashtable   groupMembers= new Hashtable();
863     Hashtable   userNames= new Hashtable();
864     Hashtable   userGroups= new Hashtable();
865 
866     try {
867       stmt = this.jdbcConn.createStatement();
868 
869       //1.1 read groups
870       sql = " SELECT grp_id, " +
871             "        grp_name "+
872             " FROM   "+Gate.DB_OWNER+".t_group";
873       rs = stmt.executeQuery(sql);
874 
875 
876 
877       while (rs.next()) {
878         //access by index is faster
879         //first column index is 1
880         long grp_id = rs.getLong(1);
881         String grp_name = rs.getString(2);
882         groupNames.put(new Long(grp_id),grp_name);
883         groupMembers.put(new Long(grp_id),new Vector());
884       }
885       DBHelper.cleanup(rs);
886 
887 
888       //1.2 read users
889       sql = " SELECT usr_id, " +
890             "        usr_login "+
891             " FROM   "+Gate.DB_OWNER+".t_user";
892       rs = stmt.executeQuery(sql);
893 
894       while (rs.next()) {
895         //access by index is faster
896         //first column index is 1
897         long usr_id = rs.getLong(1);
898         String usr_name = rs.getString(2);
899         userNames.put(new Long(usr_id),usr_name);
900         userGroups.put(new Long(usr_id),new Vector());
901       }
902       DBHelper.cleanup(rs);
903 
904 
905       //1.3 read user/group relations
906       sql = " SELECT    UGRP_GROUP_ID, " +
907             "           UGRP_USER_ID "+
908             " FROM      "+Gate.DB_OWNER+".t_user_group " +
909             " ORDER BY  UGRP_GROUP_ID asc";
910       rs = stmt.executeQuery(sql);
911 
912       while (rs.next()) {
913         //access by index is faster
914         //first column index is 1
915         Long grp_id = new Long(rs.getLong(1));
916         Long usr_id = new Long(rs.getLong(2));
917 
918         //append user to group members list
919         Vector currMembers = (Vector)groupMembers.get(grp_id);
920         currMembers.add(usr_id);
921 
922         Vector currGroups = (Vector)userGroups.get(usr_id);
923         currGroups.add(grp_id);
924       }
925       DBHelper.cleanup(rs);
926     }
927     catch(SQLException sqle) {
928       throw new PersistenceException("DB error is: ["+
929                                           sqle.getMessage()+"]");
930     }
931     finally {
932       DBHelper.cleanup(rs);
933       DBHelper.cleanup(stmt);
934     }
935 
936     //2. create USerImpl's and GroupImpl's and put them in collections
937 
938     //2.1 create Groups
939     Vector toBeInitializedGroups = new Vector();
940 
941     Enumeration enGroups = groupNames.keys();
942     while (enGroups.hasMoreElements()) {
943       Long grpId = (Long)enGroups.nextElement();
944 //      Vector grpMembers = (Vector)groupMembers.get(grpId);
945       String grpName = (String)groupNames.get(grpId);
946 
947       //note that the Vector with group members is empty
948       //will beinitalized later (ugly hack for bad desgin)
949       GroupImpl grp = new GroupImpl(grpId,grpName,new Vector(),this,this.jdbcConn);
950       //register as listener for thsi group
951       //we care only about name changes
952       grp.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
953 
954       //add to collection
955       this.groupsByID.put(grp.getID(),grp);
956       this.groupsByName.put(grp.getName(),grp);
957 
958       //add to vector of the objects to be initialized
959       toBeInitializedGroups.add(grp);
960     }
961 
962     //2.2 create Users
963     Vector toBeInitializedUsers = new Vector();
964 
965     Enumeration enUsers = userNames.keys();
966     while (enUsers.hasMoreElements()) {
967       Long usrId = (Long)enUsers.nextElement();
968 //      Vector usrGroups = (Vector)userGroups.get(usrId);
969       String usrName = (String)userNames.get(usrId);
970 
971       //note that the Vector with user's group is empty
972       //will be initalized later (ugly hack for bad desgin)
973       UserImpl usr = new UserImpl(usrId,usrName,new Vector(),this,this.jdbcConn);
974       //register as listener for thsi user
975       //we care only about user changing name
976       usr.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
977 
978       //add to collection
979       this.usersByID.put(usr.getID(),usr);
980       this.usersByName.put(usr.getName(),usr);
981 
982       //add to vector of the objects to be initialized
983       toBeInitializedUsers.add(usr);
984     }
985 
986     //3. the hack itself:
987     //all the groups and users are not fully initialized yet
988     //(the groups/users Vectors are empty)
989     //initialize them now
990 
991     //3.1 initialize groups
992     for (int i=0; i< toBeInitializedGroups.size(); i++) {
993       GroupImpl grp = (GroupImpl)toBeInitializedGroups.elementAt(i);
994       grp.setUsers((Vector)groupMembers.get(grp.getID()));
995     }
996 
997     //3.2 initialize users
998     for (int i=0; i< toBeInitializedUsers.size(); i++) {
999       UserImpl usr = (UserImpl)toBeInitializedUsers.elementAt(i);
1000      usr.setGroups((Vector)userGroups.get(usr.getID()));
1001    }
1002
1003  }
1004
1005
1006  private void fireObjectCreatedEvent(ObjectModificationEvent e) {
1007
1008    //sanity check
1009    if (e.getType() != ObjectModificationEvent.OBJECT_CREATED) {
1010      throw new IllegalArgumentException();
1011    }
1012
1013    for (int i=0; i< this.omCreationListeners.size(); i++) {
1014      ((ObjectModificationListener)this.omCreationListeners.elementAt(i)).objectCreated(e);
1015    }
1016  }
1017
1018
1019  private void fireObjectDeletedEvent(ObjectModificationEvent e) {
1020
1021    //sanity check
1022    if (e.getType() != ObjectModificationEvent.OBJECT_DELETED) {
1023      throw new IllegalArgumentException();
1024    }
1025
1026    for (int i=0; i< this.omDeletionListeners.size(); i++) {
1027      ((ObjectModificationListener)this.omDeletionListeners.elementAt(i)).objectDeleted(e);
1028    }
1029  }
1030
1031
1032  private void fireObjectModifiedEvent(ObjectModificationEvent e) {
1033
1034    //sanity check
1035    if (e.getType() != ObjectModificationEvent.OBJECT_MODIFIED) {
1036      throw new IllegalArgumentException();
1037    }
1038
1039    for (int i=0; i< this.omModificationListeners.size(); i++) {
1040      ((ObjectModificationListener)omModificationListeners.elementAt(i)).objectModified(e);
1041    }
1042  }
1043
1044
1045
1046
1047  public void registerObjectModificationListener(ObjectModificationListener l,
1048                                                 int eventType) {
1049
1050    if (eventType != ObjectModificationEvent.OBJECT_CREATED &&
1051        eventType != ObjectModificationEvent.OBJECT_DELETED &&
1052        eventType != ObjectModificationEvent.OBJECT_MODIFIED) {
1053
1054        throw new IllegalArgumentException();
1055    }
1056
1057    switch(eventType) {
1058      case ObjectModificationEvent.OBJECT_CREATED :
1059        this.omCreationListeners.add(l);
1060        break;
1061      case ObjectModificationEvent.OBJECT_DELETED :
1062        this.omDeletionListeners.add(l);
1063        break;
1064      case ObjectModificationEvent.OBJECT_MODIFIED :
1065        this.omModificationListeners.add(l);
1066        break;
1067      default:
1068        Assert.fail();
1069    }
1070
1071  }
1072
1073  public void unregisterObjectModificationListener(ObjectModificationListener l,
1074                                                   int eventType) {
1075
1076    if (eventType != ObjectModificationEvent.OBJECT_CREATED &&
1077        eventType != ObjectModificationEvent.OBJECT_DELETED &&
1078        eventType != ObjectModificationEvent.OBJECT_MODIFIED) {
1079
1080        throw new IllegalArgumentException();
1081    }
1082
1083    switch(eventType) {
1084      case ObjectModificationEvent.OBJECT_CREATED :
1085        this.omCreationListeners.remove(l);
1086        break;
1087      case ObjectModificationEvent.OBJECT_DELETED :
1088        this.omDeletionListeners.remove(l);
1089        break;
1090      case ObjectModificationEvent.OBJECT_MODIFIED :
1091        this.omModificationListeners.remove(l);
1092        break;
1093      default:
1094        Assert.fail();
1095    }
1096
1097  }
1098
1099
1100
1101
1102  /* ObjectModificationListener methods */
1103
1104  public void objectCreated(ObjectModificationEvent e) {
1105    //I've never registered for these events
1106    Assert.fail();
1107  }
1108
1109  public void objectModified(ObjectModificationEvent e) {
1110
1111    Object source = e.getSource();
1112    int type = e.getType();
1113    int subtype = e.getSubType();
1114
1115    //sanity checks
1116    if (type != ObjectModificationEvent.OBJECT_MODIFIED) {
1117      throw new IllegalArgumentException();
1118    }
1119
1120    //I'm interested only in Groups and Users
1121    if (false == source instanceof Group &&
1122        false == source instanceof User) {
1123
1124      throw new IllegalArgumentException();
1125    }
1126
1127
1128    if (source instanceof Group) {
1129
1130      Assert.assertTrue(subtype == Group.OBJECT_CHANGE_ADDUSER ||
1131                    subtype == Group.OBJECT_CHANGE_NAME ||
1132                    subtype == Group.OBJECT_CHANGE_REMOVEUSER);
1133
1134      //the name of the group could be different now (IDs are fixed)
1135      if (subtype == Group.OBJECT_CHANGE_NAME) {
1136        //rehash
1137        //any better idea how to do it?
1138        Set mappings = this.groupsByName.entrySet();
1139        Iterator it = mappings.iterator();
1140
1141        boolean found = false;
1142        while (it.hasNext()) {
1143          Map.Entry mapEntry = (Map.Entry)it.next();
1144          String key = (String)mapEntry.getKey();
1145          Group  grp = (Group)mapEntry.getValue();
1146
1147          if (false == key.equals(grp.getName())) {
1148            //gotcha
1149            this.groupsByName.remove(key);
1150            this.groupsByName.put(grp.getName(),grp);
1151            found = true;
1152            break;
1153          }
1154        }
1155
1156        Assert.assertTrue(found);
1157      }
1158    }
1159    else {
1160
1161      Assert.assertTrue(source instanceof User);
1162
1163      //the name of the user could be different now (IDs are fixed)
1164
1165      Assert.assertTrue(subtype == User.OBJECT_CHANGE_NAME);
1166
1167      //the name of the group could be different now (IDs are fixed)
1168      if (subtype == User.OBJECT_CHANGE_NAME) {
1169        //rehash
1170        //any better idea how to do it?
1171        Set mappings = this.usersByName.entrySet();
1172        Iterator it = mappings.iterator();
1173
1174        boolean found = false;
1175        while (it.hasNext()) {
1176          Map.Entry mapEntry = (Map.Entry)it.next();
1177          String key = (String)mapEntry.getKey();
1178          User  usr = (User)mapEntry.getValue();
1179
1180          if (false == key.equals(usr.getName())) {
1181            //gotcha
1182            this.usersByName.remove(key);
1183            this.usersByName.put(usr.getName(),usr);
1184            found = true;
1185            break;
1186          }
1187        }
1188
1189        Assert.assertTrue(found);
1190      }
1191    }
1192
1193
1194  }
1195
1196  public void objectDeleted(ObjectModificationEvent e) {
1197    //I've never registered for these events
1198    Assert.fail();
1199  }
1200
1201  public void processGateEvent(GateEvent e){
1202    throw new MethodNotImplementedException();
1203  }
1204
1205  /** -- */
1206  public boolean isValidSecurityInfo(SecurityInfo si) {
1207
1208    switch(si.getAccessMode()) {
1209
1210      case SecurityInfo.ACCESS_WR_GW:
1211      case SecurityInfo.ACCESS_GR_GW:
1212        return (null != si.getGroup());
1213
1214      case SecurityInfo.ACCESS_GR_OW:
1215        return (null != si.getGroup() &&
1216                null != si.getUser());
1217
1218      case SecurityInfo.ACCESS_OR_OW:
1219        return (null != si.getUser());
1220
1221      default:
1222        throw new IllegalArgumentException();
1223    }
1224  }
1225
1226  public void finalize() {
1227    //close connection
1228    try {
1229      this.jdbcConn.close();
1230    }
1231    catch(SQLException sqle) {}
1232
1233  }
1234
1235}
1236