KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jcorporate > expresso > core > controller > DBController


1 /* ====================================================================
2  * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
3  *
4  * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the
16  * distribution.
17  *
18  * 3. The end-user documentation included with the redistribution,
19  * if any, must include the following acknowledgment:
20  * "This product includes software developed by Jcorporate Ltd.
21  * (http://www.jcorporate.com/)."
22  * Alternately, this acknowledgment may appear in the software itself,
23  * if and wherever such third-party acknowledgments normally appear.
24  *
25  * 4. "Jcorporate" and product names such as "Expresso" must
26  * not be used to endorse or promote products derived from this
27  * software without prior written permission. For written permission,
28  * please contact info@jcorporate.com.
29  *
30  * 5. Products derived from this software may not be called "Expresso",
31  * or other Jcorporate product names; nor may "Expresso" or other
32  * Jcorporate product names appear in their name, without prior
33  * written permission of Jcorporate Ltd.
34  *
35  * 6. No product derived from this software may compete in the same
36  * market space, i.e. framework, without prior written permission
37  * of Jcorporate Ltd. For written permission, please contact
38  * partners@jcorporate.com.
39  *
40  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
41  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
42  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
44  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
46  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
47  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
49  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
50  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51  * SUCH DAMAGE.
52  * ====================================================================
53  *
54  * This software consists of voluntary contributions made by many
55  * individuals on behalf of the Jcorporate Ltd. Contributions back
56  * to the project(s) are encouraged when you make modifications.
57  * Please send them to support@jcorporate.com. For more information
58  * on Jcorporate Ltd. and its products, please see
59  * <http://www.jcorporate.com/>.
60  *
61  * Portions of this software are based upon other open source
62  * products and are subject to their respective licenses.
63  */

64
65 /**
66  *
67  * Copyright 1999, 2000, 2001 Jcorporate Ltd.
68  */

69 package com.jcorporate.expresso.core.controller;
70
71 import com.jcorporate.expresso.core.cache.CacheException;
72 import com.jcorporate.expresso.core.cache.CacheManager;
73 import com.jcorporate.expresso.core.cache.CacheSystem;
74 import com.jcorporate.expresso.core.db.DBException;
75 import com.jcorporate.expresso.core.dbobj.ValidValue;
76 import com.jcorporate.expresso.core.i18n.Messages;
77 import com.jcorporate.expresso.core.security.User;
78 import com.jcorporate.expresso.kernel.util.FastStringBuffer;
79 import com.jcorporate.expresso.services.dbobj.ControllerSecurity;
80 import com.jcorporate.expresso.services.dbobj.DefaultUserInfo;
81 import com.jcorporate.expresso.services.dbobj.GroupMembers;
82 import com.jcorporate.expresso.services.dbobj.UserGroup;
83 import org.apache.log4j.Logger;
84
85 import java.util.Enumeration JavaDoc;
86 import java.util.Hashtable JavaDoc;
87 import java.util.Iterator JavaDoc;
88 import java.util.Stack JavaDoc;
89 import java.util.StringTokenizer JavaDoc;
90 import java.util.Vector JavaDoc;
91
92
93 /**
94  * An extension of the basic Controller object that knows how to
95  * connect to a database, and how to read it's own security
96  * information for each state
97  */

98 public abstract class DBController extends Controller {
99
100     private static final String JavaDoc CACHE_NAME = DBController.class + "securityCache"; // includes class name and hashcode
101

102     /**
103      * Default TTY for the security cache = 30 minutes
104      */

105     private static final long CACHE_TTY = 60 * 1000 * 30;
106
107     private static boolean listenersSetup = false;
108     /**
109      * sLog for static methods in this class alone
110      */

111     private static Logger sLog = Logger.getLogger(DBController.class);
112
113     /**
114      *
115      */

116     public DBController() {
117         if (!listenersSetup) {
118             CacheManager.addListener(CACHE_NAME, UserGroup.class.getName());
119             CacheManager.addListener(CACHE_NAME, ControllerSecurity.class.getName());
120             CacheManager.addListener(CACHE_NAME, DefaultUserInfo.class.getName());
121             CacheManager.addListener(CACHE_NAME, GroupMembers.class.getName());
122
123             listenersSetup = true;
124         }
125     } /* DBController() */
126
127     /**
128      * Revised method of cached security. Stores keys based upon group name
129      * rather than uid. This allows users to share the same cache entry without
130      * additional round-trips to the database.
131      *
132      * @param group The group name to add
133      * @param dataContext the datacontext to set this value for
134      * @param className the classname as the controller key
135      * @param states the states that are allowed for this group and this class
136      * @throws CacheException upon error working with the cache system.
137      */

138     private static synchronized void addCachedSecurity(String JavaDoc group,
139                                                        String JavaDoc dataContext, String JavaDoc className, String JavaDoc states)
140             throws CacheException {
141         CacheSystem cs = CacheManager.getCacheSystem(dataContext);
142         if (cs == null) {
143             //We aren't caching for this context.
144
return;
145         }
146
147         if (sLog.isDebugEnabled()) {
148             sLog.debug("Adding cached access for Group:'" + group + "' for state(s) '" +
149                     states + "' controller " + className);
150         }
151         if (!cs.existsCache(CACHE_NAME)) {
152             cs.createCache(CACHE_NAME, false);
153             cs.addListener(CACHE_NAME, UserGroup.class.getName());
154             cs.addListener(CACHE_NAME, ControllerSecurity.class.getName());
155             cs.addListener(CACHE_NAME, DefaultUserInfo.class.getName());
156             cs.addListener(CACHE_NAME, GroupMembers.class.getName());
157
158         }
159
160         /* If there is already an entry for this user, add the given */
161
162         /* state(s) to the list of allowed things - no dups */
163         ValidValue existingVal = (ValidValue) cs.getItem(CACHE_NAME,
164                 group + "|" +
165                 className);
166
167         if (existingVal != null) {
168             String JavaDoc existing = existingVal.getDescription();
169
170             /* Using the two hashtable below "merges" the entries, creatiing */
171
172             /* one hash with all of the states in it */
173             Hashtable JavaDoc existingHash = new Hashtable JavaDoc(3);
174             String JavaDoc oneState = null;
175             StringTokenizer JavaDoc stk = new StringTokenizer JavaDoc(existing, " ");
176
177             while (stk.hasMoreElements()) {
178                 oneState = stk.nextToken();
179                 existingHash.put(oneState, oneState);
180             }
181
182             StringTokenizer JavaDoc stk2 = new StringTokenizer JavaDoc(states, " ");
183
184             while (stk2.hasMoreElements()) {
185                 oneState = stk2.nextToken();
186                 existingHash.put(oneState, oneState);
187             }
188             /* Time-saver: If we have "*", don't bother with the rest */
189             if (existingHash.get("*") != null) {
190                 existing = ("*");
191             } else {
192
193                 FastStringBuffer existingBuffer = FastStringBuffer.getInstance();
194                 try {
195                     for (Enumeration JavaDoc e = existingHash.keys(); e.hasMoreElements();) {
196                         existingBuffer.append((String JavaDoc) e.nextElement());
197                         existingBuffer.append(" ");
198                     }
199
200                     existing = existingBuffer.toString();
201                 } finally {
202                     existingBuffer.release();
203                     existingBuffer = null;
204                 }
205             }
206
207             cs.removeItem(CACHE_NAME, existingVal);
208             cs.addItem(CACHE_NAME,
209                     new ValidValue(group + "|" + className,
210                             existing), CACHE_TTY);
211
212             if (sLog.isDebugEnabled()) {
213                 sLog.debug("Cache updated for group '" + group + "', controller " +
214                         className + ", permissions now '" + existing + "'");
215             }
216
217             return;
218         } else {
219             if (sLog.isDebugEnabled()) {
220                 sLog.debug("There was no existing security in cache");
221             }
222
223             cs.addItem(CACHE_NAME,
224                     new ValidValue(group + "|" + className, states),
225                     CACHE_TTY);
226         }
227
228     }
229
230
231     /**
232      * Checks a 'state field' string and checks to see if it's allowed. Searches
233      * for either a match in a comma delimited version OR a wildcard &quot;*&quot;
234      *
235      * @param searchString the description string to parse
236      * @param state the state we're looking to match.
237      * @return boolean, true if we've found an allowed state.
238      */

239     private static boolean containsAllowedState(String JavaDoc searchString, String JavaDoc state) {
240         if (searchString.equals("*")) {
241             return true;
242         }
243
244         if (searchString == null || searchString.length() == 0) {
245             return false;
246         }
247
248         StringTokenizer JavaDoc stok = new StringTokenizer JavaDoc(searchString, ",");
249
250         while (stok.hasMoreTokens()) {
251             String JavaDoc oneState = stok.nextToken().trim();
252             if (oneState.equals(state)) {
253                 return true;
254             }
255         }
256
257         return false;
258     }
259
260     /**
261      * For database controllers, we check if the new state is allowed
262      * against the database objects for that purpose
263      *
264      * @param newState The name of the new state that is being requested; controller class is assumed to be 'this'
265      * @param myRequest the <code>ControllerRequest</code> object
266      * @return True if the state is permitted for this user, else false
267      * @throws ControllerException if another undefined error takes place while
268      * checking security.
269      */

270     public boolean stateAllowed(String JavaDoc newState,
271                                 ControllerRequest myRequest)
272             throws ControllerException {
273         try {
274             boolean allowed = isAllowed(myRequest, this, newState);
275
276             return allowed;
277         } catch (DBException de) {
278             throw new ControllerException("Unable to check Controller " +
279                     "security", de);
280         } catch (CacheException ce) {
281             throw new ControllerException("Cache exception checking " +
282                     "Controller security", ce);
283         }
284     } /* stateAllowed(String) */
285
286     /**
287      * for the given controller class and state, can the user in this request access this state?
288      *
289      * @param request current request
290      * @param controller controller in question
291      * @param newState state in question
292      * @return true if state is allowed for user specified in request
293      * @throws DBException upon database error
294      * @throws CacheException upon error working with the cache system.
295      */

296     public static boolean isAllowed(ControllerRequest request, DBController controller, String JavaDoc newState) throws DBException,
297             CacheException {
298         boolean allowed = false;
299
300         if (controller == null) {
301             return false;
302         }
303
304         //Grab the user
305
User thisUser = new User();
306         thisUser.setDataContext(request.getDataContext());
307         thisUser.setUid(request.getUid());
308         thisUser.retrieve();
309
310         // admin is always allowed
311
if (thisUser.isAdmin()) {
312             return true;
313         }
314
315         String JavaDoc controllerClassName = controller.getClass().getName();
316         //This gets filled with any group names that weren't in the cache
317
//
318
String JavaDoc missingGroupNames[] = null;
319
320
321         //Group the user's groups
322
Vector JavaDoc usersGroups = thisUser.getGroups();
323         int groupSize = usersGroups.size();
324
325         //Precalc to save time
326
String JavaDoc keySuffix = "|" + controllerClassName;
327
328         CacheSystem cs = CacheManager.getCacheSystem(request.getDataContext());
329
330         //Ok, here's the critical section
331
//If we get a batch of users logging in at once, we don't want
332
//to crash the security system. So! Only allow one thread
333
//at a time to check the cache system. Otherwise, we'll get
334
//100 database hits when we only need one.
335
synchronized (DBController.class) {
336             //Make sure there's at least some sort of upper bound in what the cache
337
//holds so that we don't run out of memory.
338
if (!cs.existsCache(CACHE_NAME)) {
339                 cs.createCache(CACHE_NAME, false, 500);
340                 //We'll need to change things if somebody manually changes the
341
//controller security OR group members are added or deleted
342
//If the other caches are modified, it clears the dbobject
343
//security cache
344
cs.addListener(CACHE_NAME, UserGroup.class.getName());
345                 cs.addListener(CACHE_NAME, ControllerSecurity.class.getName());
346                 cs.addListener(CACHE_NAME, DefaultUserInfo.class.getName());
347                 cs.addListener(CACHE_NAME, GroupMembers.class.getName());
348             }
349
350             //
351
//We're looking for the 'best case' here. If we find any group
352
//that it is valid for, then we're set. If we don't find anything
353
//positive in the cache and there are groups that aren't currently
354
//cached, then we need to go to the database and find the remaining
355
//items.
356
//
357
int insertGroupIndex = 0;
358             for (int i = 0; i < groupSize; i++) {
359                 String JavaDoc oneGroup = (String JavaDoc) usersGroups.get(i);
360                 String JavaDoc key = oneGroup + keySuffix;
361                 ValidValue secVal = (ValidValue) cs.getItem(CACHE_NAME, key);
362
363                 if (secVal == null) {
364                     //No cache item exists in this value. If we fail to find
365
//a positive we have to go to the database to check for this
366
//group
367
if (missingGroupNames == null) {
368                         missingGroupNames = new String JavaDoc[groupSize];
369                     }
370                     missingGroupNames[insertGroupIndex] = oneGroup;
371                     insertGroupIndex++;
372                 } else {
373                     //Ok we found something. If it's a positive, then we
374
//return true, otherwise we keep searching.
375
String JavaDoc sec = secVal.getDescription();
376
377                     if (containsAllowedState(sec, newState)) {
378                         if (sLog.isDebugEnabled()) {
379                             sLog.debug("User '" + request.getUid() +
380                                     "' allowed state '" + newState +
381                                     "' via cache");
382                         }
383
384                         allowed = true;
385                         break;
386                     }
387                 }
388             }
389
390             if (!allowed && missingGroupNames != null) {
391                 //
392
//Ok. Getting here means that there are group names we haven't
393
//covered all groups so we have to go to the database to check them.
394
//If missingGroupNames == null then we haven't found a match and
395
//should return false
396
//
397
ControllerSecurity csec = new ControllerSecurity();
398                 csec.setDataContext(request.getDataContext());
399
400                 ControllerSecurity oneSecurityEntry = null;
401                 String JavaDoc allowedStates = null;
402
403                 for (int i = 0; i < missingGroupNames.length; i++) {
404                     String JavaDoc oneGroupName = missingGroupNames[i];
405                     if (oneGroupName != null) {
406
407                         csec.clear();
408                         csec.setField(ControllerSecurity.CONTROLLER_CLASS, controllerClassName);
409                         csec.setField(ControllerSecurity.GROUP_NAME, oneGroupName);
410
411                         for (Iterator JavaDoc cse = csec.searchAndRetrieveList().iterator();
412                              cse.hasNext();) {
413
414                             oneSecurityEntry = (ControllerSecurity) cse.next();
415                             allowedStates = oneSecurityEntry.getField(ControllerSecurity.STATES);
416
417                             if (containsAllowedState(allowedStates, newState)) {
418                                 allowed = true;
419                             }
420                             addCachedSecurity(oneGroupName,
421                                     request.getDataContext(),
422                                     controllerClassName, allowedStates);
423
424                         } /* for */
425                     } else {
426                         break; // we don't fill array necessarily
427
}
428                 } /* for each user group */
429             }
430         } //end synchronized
431
return allowed;
432     }
433
434
435     /**
436      * Override the superclass getString, as now we know the user
437      * and the DB, and thus can use the language preferences of the user
438      * if available.
439      *
440      * @param stringCode The code in the MessagesBundle to retrieve
441      * @param args the i18n formatting arguments
442      * @param myRequest the controllerRequest object
443      * @return the properly formatted string
444      * @deprecated The getString method from ControllerRequest should
445      * be used in *all* cases - this method is not reliable, as the
446      * same DBController instance is shared between users.
447      */

448     protected String JavaDoc getString(String JavaDoc stringCode, Object JavaDoc[] args,
449                                ControllerRequest myRequest) {
450         if (myRequest.getUser().equals("")) {
451             return super.getString(stringCode, args);
452         } else {
453 // return Messages.getStringForUser(myRequest.getUid(),
454
// myRequest.getDBName(),
455
// getSchema(), stringCode, args);
456

457             Stack JavaDoc s = this.getSchemaStack();
458             if (s == null || s.isEmpty()) {
459                 return Messages.getStringForUser(myRequest.getUid(),
460                         myRequest.getDataContext(), getSchema(), stringCode,
461                         args);
462             } else {
463                 for (Iterator JavaDoc i = s.iterator(); i.hasNext();) {
464                     try {
465                         String JavaDoc schema = (String JavaDoc) i.next();
466                         return Messages.getStringForUser(myRequest.getUid()
467                                 , myRequest.getDataContext(), schema, stringCode,
468                                 args);
469                     } catch (IllegalArgumentException JavaDoc ex) {
470                         if (!i.hasNext()) {
471                             throw ex;
472                         }
473                     }
474
475                 }
476
477                 throw new IllegalArgumentException JavaDoc("Unable to locate string " + stringCode
478                         + " for any schema");
479             }
480         }
481     } /* getString(String, Object[]) */
482
483 } /* DBController */
484
Popular Tags