001: /* Copyright 2001, 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal;
007:
008: import java.sql.Connection;
009: import java.sql.PreparedStatement;
010: import java.sql.ResultSet;
011: import java.sql.SQLException;
012: import java.sql.Statement;
013: import java.util.ArrayList;
014: import java.util.Collection;
015: import java.util.HashMap;
016: import java.util.Iterator;
017: import java.util.Map;
018:
019: import javax.sql.DataSource;
020:
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023: import org.jasig.portal.services.SequenceGenerator;
024:
025: /**
026: * This class provides access to the entity types used by <code>IBasicEntities</code>
027: * and the classes in <code>org.jasig.portal.groups</code> and
028: * <code>org.jasig.portal.concurrency</code>.
029: * <p>
030: * Each type is associated with an <code>Integer</code> used to represent the
031: * type in the portal data store. This class translates between the
032: * <code>Integer</code> and <code>Class</code> values.
033: *
034: * @author Dan Ellentuck
035: * @version $Revision: 36731 $
036: * @see org.jasig.portal.IBasicEntity
037: */
038: public class EntityTypes {
039:
040: private static final Log log = LogFactory.getLog(EntityTypes.class);
041:
042: private static EntityTypes singleton;
043:
044: // Caches for EntityType instances.
045: private Map entityTypesByID;
046: private Map entityTypesByType;
047:
048: // Lock for crud operations.
049: private Object updateLock = new Object();
050:
051: // Constant strings for ENTITY TYPE table:
052: private static String ENTITY_TYPE_TABLE = "UP_ENTITY_TYPE";
053: private static String TYPE_ID_COLUMN = "ENTITY_TYPE_ID";
054: private static String TYPE_NAME_COLUMN = "ENTITY_TYPE_NAME";
055: private static String DESCRIPTIVE_NAME_COLUMN = "DESCRIPTIVE_NAME";
056:
057: // For retrieving all types:
058: public static int NULL_TYPE_ID = -1;
059:
060: public static Class GROUP_ENTITY_TYPE = org.jasig.portal.groups.IEntityGroup.class;
061: public static Class LEAF_ENTITY_TYPE = org.jasig.portal.groups.IEntity.class;
062:
063: private class EntityType {
064: private Class type;
065: private Integer typeId;
066: private String descriptiveName;
067:
068: private EntityType(Class cl, Integer id, String description) {
069: super ();
070: type = cl;
071: typeId = id;
072: descriptiveName = description;
073: }
074:
075: private Class getType() {
076: return type;
077: }
078:
079: private Integer getTypeId() {
080: return typeId;
081: }
082:
083: private String getDescriptiveName() {
084: return descriptiveName;
085: }
086:
087: public String toString() {
088: String desc = (descriptiveName) == null ? ""
089: : descriptiveName;
090: return desc + " (" + getTypeId() + ") "
091: + getType().getName();
092: }
093: }
094:
095: private EntityTypes(DataSource ds) {
096: super ();
097: initialize(ds);
098: }
099:
100: /**
101: * Add the new type if it does not already exist.
102: */
103: public static void addIfNecessary(Class newType, String description)
104: throws java.lang.Exception {
105: singleton().addEntityTypeIfNecessary(newType, description);
106: }
107:
108: /**
109: * Add the new type if it does not already exist in the cache.
110: */
111: private void addEntityType(Class newType, String description)
112: throws java.lang.Exception {
113: if (getEntityTypesByType().get(newType) == null) {
114: int nextKey = getNextKey();
115: EntityType et = new EntityType(newType,
116: new Integer(nextKey), description);
117: insertEntityType(et);
118: primAddEntityType(et);
119: }
120: }
121:
122: /**
123: * Check if we have the type in our cache. If not, re-retrieve. Someone
124: * might have added it since we last retrieved. If the type is not
125: * found, try to add it to the store. If the add is not successful,
126: * re-retrieve again. If the type is still not found, rethrow the
127: * SQLException. Synchronize on update lock to serialize adds, deletes
128: * and updates while letting reads proceed.
129: */
130: public void addEntityTypeIfNecessary(Class newType,
131: String description) throws java.lang.Exception {
132: synchronized (updateLock) {
133: if (getEntityTypesByType().get(newType) == null) {
134: refresh();
135: if (getEntityTypesByType().get(newType) == null) {
136: try {
137: addEntityType(newType, description);
138: } catch (Exception ex) {
139: refresh();
140: if (getEntityTypesByType().get(newType) == null) {
141: String errString = "Attempt to add entity type failed: "
142: + ex.getMessage();
143: log.error(errString, ex);
144: throw ex;
145: }
146: } // end catch
147: } // end if
148: } // end if
149: } // end synchronized
150: }
151:
152: /**
153: * Synchronize on update lock to serialize adds, deletes and updates
154: * while letting reads proceed.
155: */
156: public void deleteEntityType(Class type) throws SQLException {
157: synchronized (updateLock) {
158: refresh();
159: EntityType et = (EntityType) getEntityTypesByType().get(
160: type);
161: if (et != null) {
162: deleteEntityType(et);
163: primRemoveEntityType(et);
164: }
165: }
166: }
167:
168: /**
169: * delete EntityType from the store.
170: */
171: private void deleteEntityType(EntityType et) throws SQLException {
172: Connection conn = null;
173: PreparedStatement ps = null;
174: try {
175: conn = RDBMServices.getConnection();
176: try {
177: ps = conn.prepareStatement(getDeleteEntityTypeSql());
178:
179: ps.setInt(1, et.getTypeId().intValue());
180: ps.setString(2, et.getType().getName());
181:
182: if (log.isDebugEnabled())
183: log.debug("EntityTypes.deleteEntityType(): " + ps
184: + "(" + et.getTypeId() + ", "
185: + et.getType() + ")");
186:
187: int rc = ps.executeUpdate();
188:
189: if (rc != 1) {
190: String errString = "Problem deleting type " + et;
191: log.error(errString);
192: throw new SQLException(errString);
193: }
194: } finally {
195: try {
196: if (ps != null) {
197: ps.close();
198: }
199: } finally {
200: RDBMServices.releaseConnection(conn);
201: }
202: }
203: } catch (java.sql.SQLException sqle) {
204: log.error("Exception deleting entity type [" + et + "]",
205: sqle);
206: throw sqle;
207: }
208: }
209:
210: /**
211: * @return java.lang.String
212: */
213: private static java.lang.String getAllColumnNames() {
214: return TYPE_ID_COLUMN + ", " + TYPE_NAME_COLUMN + ", "
215: + DESCRIPTIVE_NAME_COLUMN;
216: }
217:
218: /**
219: * @return java.util.Iterator
220: */
221: public java.util.Iterator getAllEntityTypeIDs() {
222: return entityTypesByID.keySet().iterator();
223: }
224:
225: /**
226: * @return java.util.Iterator
227: */
228: public java.util.Iterator getAllEntityTypes() {
229: Collection types = new ArrayList(getEntityTypesByType().size());
230: for (Iterator i = entityTypesByID.values().iterator(); i
231: .hasNext();) {
232: EntityType et = (EntityType) i.next();
233: types.add(et.getType());
234: }
235: return types.iterator();
236: }
237:
238: /**
239: * @return java.lang.String
240: */
241: private static java.lang.String getDeleteEntityTypeSql() {
242: return "DELETE FROM " + ENTITY_TYPE_TABLE + " WHERE "
243: + TYPE_ID_COLUMN + " = ? AND " + TYPE_NAME_COLUMN
244: + " = ?";
245: }
246:
247: /**
248: * Interface to the entity types cache.
249: * @return java.lang.String
250: */
251: public static String getDescriptiveName(Class type) {
252: return singleton().getDescriptiveNameForType(type);
253: }
254:
255: /**
256: * Interface to the entity types cache.
257: * @return java.lang.String
258: */
259: public String getDescriptiveNameForType(Class type) {
260: EntityType et = (EntityType) getEntityTypesByType().get(type);
261: return et.getDescriptiveName();
262: }
263:
264: /**
265: * Interface to the entity types cache.
266: * @return java.lang.Integer
267: */
268: public Integer getEntityIDFromType(Class type) {
269: EntityType et = (EntityType) getEntityTypesByType().get(type);
270: return (et == null) ? null : et.getTypeId();
271: }
272:
273: /**
274: * Interface to the entity types cache.
275: * @return java.lang.Class
276: */
277: public static Class getEntityType(Integer typeID) {
278: return singleton().getEntityTypeFromID(typeID);
279: }
280:
281: /**
282: * Interface to the entity types cache.
283: * @return java.lang.Class
284: */
285: public Class getEntityTypeFromID(Integer id) {
286: EntityType et = (EntityType) getEntityTypesByID().get(id);
287: return (et == null) ? null : et.getType();
288: }
289:
290: /**
291: * Interface to the entity types cache.
292: * @return java.lang.Class
293: */
294: public static Integer getEntityTypeID(Class type) {
295: return singleton().getEntityIDFromType(type);
296: }
297:
298: /**
299: * @return java.util.Map
300: */
301: private synchronized Map getEntityTypesByID() {
302: return entityTypesByID;
303: }
304:
305: private Map cloneHashMap(Map m) {
306: return ((Map) ((HashMap) m).clone());
307: }
308:
309: /**
310: * @return java.util.Map
311: */
312: private synchronized Map getEntityTypesByType() {
313: return entityTypesByType;
314: }
315:
316: /**
317: * @return java.lang.String
318: */
319: private static String getInsertEntityTypeSql() {
320: return "INSERT INTO " + ENTITY_TYPE_TABLE + " ("
321: + getAllColumnNames() + ") VALUES (?, ?, ?)";
322: }
323:
324: /**
325: * @return int
326: * @exception java.lang.Exception
327: */
328: private int getNextKey() throws java.lang.Exception {
329: return SequenceGenerator.instance().getNextInt(
330: ENTITY_TYPE_TABLE);
331: }
332:
333: /**
334: * @return java.lang.String
335: */
336: private static java.lang.String getSelectEntityTypesSql() {
337: return "SELECT " + getAllColumnNames() + " FROM "
338: + ENTITY_TYPE_TABLE;
339: }
340:
341: /**
342: * @return java.lang.String
343: */
344: private static java.lang.String getUpdateEntityTypeSql() {
345: return "UPDATE " + ENTITY_TYPE_TABLE + " SET "
346: + DESCRIPTIVE_NAME_COLUMN + " = ? WHERE "
347: + TYPE_ID_COLUMN + " = ?";
348: }
349:
350: private void initialize(DataSource ds) {
351: Connection conn = null;
352: try {
353: conn = (ds == null) ? RDBMServices.getConnection() : ds
354: .getConnection();
355: initialize(conn);
356: }
357:
358: catch (Exception ex) {
359: log.error("Exception initializing cache of entity types.",
360: ex);
361: } finally {
362: if (conn != null) {
363: try {
364: if (ds == null) {
365: RDBMServices.releaseConnection(conn);
366: } else {
367: conn.close();
368: }
369: } catch (Exception ex) {
370: }
371: }
372: }
373: }
374:
375: private void initialize(Connection conn) {
376: initializeCaches();
377: Integer typeID = null;
378: Class entityType = null;
379: String description = null;
380: EntityType et = null;
381:
382: try {
383: Statement stmnt = conn.createStatement();
384: try {
385: ResultSet rs = stmnt
386: .executeQuery(getSelectEntityTypesSql());
387: try {
388: while (rs.next()) {
389: typeID = new Integer(rs.getInt(1));
390: entityType = Class.forName(rs.getString(2));
391: description = rs.getString(3);
392: et = new EntityType(entityType, typeID,
393: description);
394: primAddEntityType(et);
395: }
396: } finally {
397: rs.close();
398: }
399: } finally {
400: stmnt.close();
401: }
402: } catch (Exception ex) {
403: log.error("Exception initializing cache of entity types.",
404: ex);
405: }
406: }
407:
408: /**
409: * Cache entityTypes.
410: */
411: private void initialize() {
412: Connection conn = null;
413: try {
414: conn = RDBMServices.getConnection();
415: initialize(conn);
416: } catch (Exception ex) {
417: log.error("Exception initializing cache of entity types.",
418: ex);
419: } finally {
420: if (conn != null) {
421: RDBMServices.releaseConnection(conn);
422: }
423: }
424: }
425:
426: /**
427: * Cache entityTypes.
428: */
429: private void initializeCaches() {
430: entityTypesByID = new HashMap(10);
431: entityTypesByType = new HashMap(10);
432: }
433:
434: /**
435: * Cache entityTypes.
436: */
437: private void insertEntityType(EntityType et) throws SQLException {
438: Connection conn = null;
439: PreparedStatement ps = null;
440: try {
441: conn = RDBMServices.getConnection();
442: try {
443: ps = conn.prepareStatement(getInsertEntityTypeSql());
444:
445: ps.setInt(1, et.getTypeId().intValue());
446: ps.setString(2, et.getType().getName());
447: ps.setString(3, et.getDescriptiveName());
448:
449: if (log.isDebugEnabled())
450: log.debug("EntityTypes.insertEntityType(): " + ps
451: + "(" + et.getTypeId() + ", "
452: + et.getType() + ", "
453: + et.getDescriptiveName() + ")");
454:
455: int rc = ps.executeUpdate();
456:
457: if (rc != 1) {
458: String errString = "Problem adding entity type "
459: + et;
460: log.error(errString);
461: throw new SQLException(errString);
462: }
463: } finally {
464: try {
465: if (ps != null) {
466: ps.close();
467: }
468: } finally {
469: RDBMServices.releaseConnection(conn);
470: }
471: }
472: } catch (java.sql.SQLException sqle) {
473: log.error("Error inserting entity type " + et, sqle);
474: throw sqle;
475: }
476: }
477:
478: /**
479: * Copy on write to prevent ConcurrentModificationExceptions.
480: */
481: private void primAddEntityType(EntityType et) {
482: Map typesByType = cloneHashMap(getEntityTypesByType());
483: typesByType.put(et.getType(), et);
484: Map typesByID = cloneHashMap(getEntityTypesByID());
485: typesByID.put(et.getTypeId(), et);
486: setEntityTypesByType(typesByType);
487: setEntityTypesByID(typesByID);
488: }
489:
490: /**
491: * Copy on write to prevent ConcurrentModificationExceptions.
492: */
493: private void primRemoveEntityType(EntityType et) {
494: Map typesByType = cloneHashMap(getEntityTypesByType());
495: typesByType.remove(et.getType());
496: Map typesByID = cloneHashMap(getEntityTypesByID());
497: typesByID.remove(et.getTypeId());
498: setEntityTypesByType(typesByType);
499: setEntityTypesByID(typesByID);
500: }
501:
502: /**
503: * Interface to the entity types cache.
504: */
505: public static synchronized void refresh() {
506: singleton().initialize();
507: }
508:
509: public synchronized void setEntityTypesByID(Map m) {
510: entityTypesByID = m;
511: }
512:
513: public synchronized void setEntityTypesByType(Map m) {
514: entityTypesByType = m;
515: }
516:
517: /**
518: * @return org.jasig.portal.EntityTypes
519: */
520: public static EntityTypes singleton() {
521: return singleton(null);
522: }
523:
524: /**
525: * @return org.jasig.portal.EntityTypes
526: */
527: public static synchronized EntityTypes singleton(DataSource ds) {
528: if (singleton == null) {
529: singleton = new EntityTypes(ds);
530: }
531: return singleton;
532: }
533:
534: /**
535: * Synchronize on update lock to serialize adds, deletes and updates
536: * while letting reads proceed.
537: */
538: public void updateEntityType(Class type, String newDescription)
539: throws Exception {
540: synchronized (updateLock) {
541: refresh();
542: EntityType et = (EntityType) getEntityTypesByType().get(
543: type);
544: if (et == null) {
545: addEntityType(type, newDescription);
546: } else {
547: et.descriptiveName = newDescription;
548: updateEntityType(et);
549: primAddEntityType(et);
550: }
551: }
552: }
553:
554: /**
555: * Cache entityTypes.
556: */
557: private void updateEntityType(EntityType et) throws SQLException {
558: Connection conn = null;
559: PreparedStatement ps = null;
560: try {
561: conn = RDBMServices.getConnection();
562: try {
563: ps = conn.prepareStatement(getUpdateEntityTypeSql());
564:
565: ps.setString(1, et.getDescriptiveName());
566: ps.setInt(2, et.getTypeId().intValue());
567:
568: if (log.isDebugEnabled())
569: log.debug("EntityTypes.updateEntityType(): " + ps
570: + "(" + et.getType() + ", "
571: + et.getDescriptiveName() + ", "
572: + et.getTypeId() + ")");
573:
574: int rc = ps.executeUpdate();
575:
576: if (rc != 1) {
577: String errString = "Problem updating type " + et;
578: log.error(errString);
579: throw new SQLException(errString);
580: }
581: } finally {
582: try {
583: if (ps != null) {
584: ps.close();
585: }
586: } finally {
587: RDBMServices.releaseConnection(conn);
588: }
589: }
590: } catch (java.sql.SQLException sqle) {
591: log.error("Exception updating entity type [" + et + "]",
592: sqle);
593: throw sqle;
594: }
595: }
596: }
|