001: /*
002: * The contents of this file are subject to the
003: * Mozilla Public License Version 1.1 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
006: *
007: * Software distributed under the License is distributed on an "AS IS"
008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
009: * See the License for the specific language governing rights and
010: * limitations under the License.
011: *
012: * The Initial Developer of the Original Code is Simulacra Media Ltd.
013: * Portions created by Simulacra Media Ltd are Copyright (C) Simulacra Media Ltd, 2004.
014: *
015: * All Rights Reserved.
016: *
017: * Contributor(s):
018: */
019: package org.openharmonise.rm.factory;
020:
021: import java.lang.reflect.*;
022: import java.sql.*;
023: import java.util.*;
024: import java.util.logging.*;
025:
026: import org.openharmonise.commons.cache.*;
027: import org.openharmonise.commons.dsi.*;
028: import org.openharmonise.commons.dsi.dml.SelectStatement;
029: import org.openharmonise.rm.DataAccessException;
030: import org.openharmonise.rm.config.*;
031: import org.openharmonise.rm.dsi.DataStoreObject;
032: import org.openharmonise.rm.resources.*;
033: import org.openharmonise.rm.resources.lifecycle.*;
034:
035: /**
036: * Extends <code>AbstractCache</code> to cache instances of the core classes
037: * in Harmonise.
038: *
039: * @author Michael Bell
040: * @version $Revision: 1.3 $
041: *
042: */
043: public final class GeneralCache extends AbstractCache implements
044: EditEventListener {
045:
046: /**
047: * The name of class cached by this object
048: */
049: private String m_sClassname = null;
050:
051: /**
052: * The data store interface
053: */
054: protected AbstractDataStoreInterface m_dsi = null;
055:
056: /**
057: * A <code>Map</code> mapping between paths and object cache keys
058: */
059: protected Map m_path_cache;
060:
061: /**
062: * An instance of the class cached by this cache
063: */
064: private AbstractObject m_xobj_instance;
065:
066: /**
067: * Constant suffix used to build the cache size config parameter name.
068: * The pattern will build paramter names as follows - 'class name' + "_cache_size"
069: */
070: private static final String PNAME_CACHESIZE = "_cache_size";
071:
072: /**
073: * Logger for this class
074: */
075: private static final Logger m_logger = Logger
076: .getLogger(GeneralCache.class.getName());
077:
078: /**
079: * Creates cache with default parameters with the given data store
080: * interface and named after the class name.
081: *
082: * @param dbinterf the data store interface
083: * @param sClassname the name of class which is cached in this cache
084: * @throws CacheException if the class name is not valid
085: */
086: public GeneralCache(AbstractDataStoreInterface dbinterf,
087: String sClassname) throws CacheException {
088: super (sClassname);
089:
090: String sObjectName = sClassname;
091:
092: if (sClassname.indexOf('.') > 0) {
093: sObjectName = sClassname.substring(sClassname
094: .lastIndexOf('.') + 1);
095: }
096:
097: String pname_cache_size = sObjectName + PNAME_CACHESIZE;
098:
099: int max_cache_size = 0;
100: int cachepage_size = 0;
101:
102: try {
103: max_cache_size = ConfigSettings.getIntProperty(
104: pname_cache_size, "" + DEFAULT_CACHESIZE);
105:
106: setCacheMaxSize(max_cache_size);
107:
108: } catch (ConfigException e) {
109: // dont care about errors here, just use the defaults
110: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
111: }
112:
113: m_path_cache = Collections.synchronizedMap(new HashMap(
114: super .DEFAULT_CACHESIZE));
115: m_dsi = dbinterf;
116: m_sClassname = sClassname;
117:
118: try {
119: Class cls = Class.forName(m_sClassname);
120: m_xobj_instance = (AbstractObject) cls.newInstance();
121: } catch (InstantiationException ins_e) {
122: throw new CacheException(
123: "Cannot instantiate command class " + m_sClassname);
124: } catch (ClassNotFoundException clss_e) {
125: throw new CacheException(m_sClassname
126: + " is not a valid harmonise object");
127: } catch (IllegalAccessException ia_e) {
128: throw new CacheException("Can't call newInstance() on "
129: + m_sClassname);
130: }
131:
132: }
133:
134: /* (non-Javadoc)
135: * @see org.openharmonise.commons.cache.AbstractCache#getCacheName()
136: */
137: public String getCacheName() {
138: return (this .m_sClassname);
139: }
140:
141: /**
142: * Returns an object from the cache which matches the given path.
143: *
144: * @param sPath the path of object to return
145: * @return an object from the cache which matches the given path.
146: * @throws CacheException if there is an error instantiating the
147: * requested object
148: */
149: public Object getObjectFromPath(String sPath) throws CacheException {
150: String sIdKey = null;
151:
152: if (m_path_cache.containsKey(sPath)) {
153: sIdKey = (String) m_path_cache.get(sPath);
154: } else {
155:
156: if (sPath.equals("/root") || sPath.lastIndexOf("/") == 0) {
157: sIdKey = this .getIdKeyFromPath(sPath);
158: }
159:
160: if ((sIdKey == null) || (sIdKey.length() == 0)
161: || (sIdKey.equals("-1") == true)) {
162: int nIndex = sPath.lastIndexOf("/");
163:
164: if (m_logger.isLoggable(Level.FINE)) {
165: m_logger.logp(Level.FINE,
166: this .getClass().getName(),
167: "getObjectFromPath", "Looking for path - "
168: + sPath);
169: }
170:
171: String sGroupName = sPath.substring(0, nIndex);
172:
173: String sGroupClassName = null;
174:
175: AbstractChildObject child = null;
176:
177: try {
178:
179: child = (AbstractChildObject) Class.forName(
180: this .m_sClassname).newInstance();
181: } catch (InstantiationException ins_e) {
182: throw new CacheException(
183: "Cannot instantiate command class "
184: + m_sClassname);
185: } catch (ClassNotFoundException clss_e) {
186: throw new CacheException(m_sClassname
187: + " is not a valid harmonise object");
188: } catch (IllegalAccessException ia_e) {
189: throw new CacheException(
190: "Can't call newInstance() on "
191: + m_sClassname);
192: }
193:
194: sGroupClassName = child.getParentObjectClassName();
195:
196: AbstractParentObject grpObj = (AbstractParentObject) CacheHandler
197: .getInstance(this .m_dsi).getObjectFromPath(
198: sGroupClassName, sGroupName);
199:
200: if (grpObj != null) {
201: Vector descendants = new Vector();
202:
203: try {
204:
205: Class objClass = Class
206: .forName(this .m_sClassname);
207:
208: //if the grpObj is an instance of this class type then
209: //this will be a AbstractParentObject and so is a branch node
210: if (objClass.isInstance(grpObj) == true) {
211: descendants
212: .addAll(grpObj
213: .getChildrenByType(AbstractParentObject.BRANCH_NODES));
214: } else {
215: descendants
216: .addAll(grpObj
217: .getChildrenByType(AbstractParentObject.LEAF_NODES));
218: }
219: } catch (ClassNotFoundException clss_e) {
220: throw new CacheException(m_sClassname
221: + " is not a valid harmonise object");
222: } catch (DataAccessException da_e) {
223: throw new CacheException(
224: "Problem occured accessing children",
225: da_e);
226: }
227:
228: boolean bFound = false;
229: int i = 0;
230:
231: while ((i < descendants.size())
232: && (bFound == false)) {
233: child = (AbstractChildObject) descendants
234: .elementAt(i);
235:
236: int nWildCardIndex = sPath.indexOf("*");
237:
238: try {
239:
240: if (nWildCardIndex > 0) {
241: if (child.getName().startsWith(
242: sPath.substring((nIndex + 1),
243: nWildCardIndex))) {
244: sIdKey = String.valueOf(child
245: .getId());
246: bFound = true;
247: }
248: } else {
249: if (child.getName().equals(
250: sPath.substring(nIndex + 1))) {
251: sIdKey = String.valueOf(child
252: .getId());
253: bFound = true;
254: }
255: }
256: } catch (DataAccessException da_e) {
257: throw new CacheException(
258: "Problem occurred finding name of child object",
259: da_e);
260: }
261:
262: i++;
263: }
264: }
265: }
266:
267: if ((sIdKey != null) && (sIdKey.length() > 0)) {
268: m_path_cache.put(sPath, sIdKey);
269: }
270: }
271:
272: if (m_logger.isLoggable(Level.FINER)) {
273: m_logger.logp(Level.FINER, this .getClass().getName(),
274: "getObjectFromPath", "Found key - " + sIdKey);
275: }
276:
277: if (sIdKey != null) {
278: return getObject(sIdKey);
279: } else {
280: return null;
281: }
282: }
283:
284: /**
285: * Create a new instance of the specified class name.
286: *
287: * @param dbintrf the data store interface to be used in constructing new instance
288: * @param sClassName the name of class to be instantiated
289: * @return a new instance of the specified class name
290: * @throws CacheException if an error occurs instantiating the new object
291: */
292: public static Object createObject(
293: AbstractDataStoreInterface dbintrf, String sClassName)
294: throws CacheException {
295: return createObject(dbintrf, sClassName, -1);
296: }
297:
298: /**
299: * Creates an instance of the specified class name with the specified id.
300: *
301: * @param dbintrf the data store interface to be used in constructing
302: * new instance
303: * @param sClassName the name of class to be instantiated
304: * @param nId the id of instance to be created
305: * @return an instance of the specified class name with the specified id
306: * @throws CacheException if an error occurs instantiating the new object
307: */
308: public static Object createObject(
309: AbstractDataStoreInterface dbintrf, String sClassName,
310: int nId) throws CacheException {
311: Object obj = null;
312:
313: try {
314: Class[] initArgsClass = null;
315: Object[] initArgs = null;
316:
317: if (nId > 0) {
318: initArgsClass = new Class[] {
319: AbstractDataStoreInterface.class, int.class };
320:
321: Integer intId = new Integer(nId);
322: initArgs = new Object[] { dbintrf, intId };
323: } else {
324: initArgsClass = new Class[] { AbstractDataStoreInterface.class };
325: initArgs = new Object[] { dbintrf };
326: }
327:
328: Class grpObjDefinition = Class.forName(sClassName);
329: Constructor initArgsConstructor = grpObjDefinition
330: .getConstructor(initArgsClass);
331: obj = (Object) initArgsConstructor.newInstance(initArgs);
332: } catch (InstantiationException ins_e) {
333: throw new CacheException(
334: "Cannot instantiate command class " + sClassName);
335: } catch (ClassNotFoundException clss_e) {
336: throw new CacheException(sClassName
337: + " is not a valid harmonise object");
338: } catch (IllegalAccessException ia_e) {
339: throw new CacheException("Can't call newInstance() on "
340: + sClassName);
341: } catch (NoSuchMethodException nsm_e) {
342: throw new CacheException("Error on method call:"
343: + nsm_e.getLocalizedMessage());
344: } catch (InvocationTargetException inv_e) {
345: throw new CacheException("Error on method call:"
346: + inv_e.getLocalizedMessage());
347: }
348:
349: return obj;
350: }
351:
352: /**
353: * Creates an instance of the specified class name with the specified name.
354: *
355: * @param dbintrf the data store interface to be used in constructing
356: * new instance
357: * @param sClassName the name of class to be instantiated
358: * @param sName the name of instance to be created
359: * @return an instance of the specified class name with the specified name
360: * @throws CacheException if an error occurs instantiating the new object
361: */
362: public static Object createObject(
363: AbstractDataStoreInterface dbintrf, String sClassName,
364: String sName) throws CacheException {
365: Object obj = null;
366:
367: try {
368: Class[] initArgsClass = null;
369: Object[] initArgs = null;
370:
371: if ((sName != null) && (sName.length() > 0)) {
372: initArgsClass = new Class[] {
373: AbstractDataStoreInterface.class, String.class };
374: initArgs = new Object[] { dbintrf, sName };
375: } else {
376: initArgsClass = new Class[] { AbstractDataStoreInterface.class };
377: initArgs = new Object[] { dbintrf };
378: }
379:
380: Class grpObjDefinition = Class.forName(sClassName);
381: Constructor initArgsConstructor = grpObjDefinition
382: .getConstructor(initArgsClass);
383: obj = (Object) initArgsConstructor.newInstance(initArgs);
384: } catch (InstantiationException ins_e) {
385: throw new CacheException(
386: "Cannot instantiate command class " + sClassName);
387: } catch (ClassNotFoundException clss_e) {
388: throw new CacheException(sClassName
389: + " is not a valid harmonise object");
390: } catch (IllegalAccessException ia_e) {
391: throw new CacheException("Can't call newInstance() on "
392: + sClassName);
393: } catch (NoSuchMethodException nsm_e) {
394: throw new CacheException("Error on method call:"
395: + nsm_e.getLocalizedMessage());
396: } catch (InvocationTargetException inv_e) {
397: throw new CacheException("Error on method call:"
398: + inv_e.getLocalizedMessage());
399: }
400:
401: return obj;
402: }
403:
404: /* (non-Javadoc)
405: * @see org.openharmonise.commons.cache.AbstractCache#changeObject(java.lang.Object, java.lang.String, java.lang.Object)
406: */
407: public void changeObject(Object cache_key, String sChangeCode,
408: Object newObject) {
409: try {
410: if (sChangeCode.equalsIgnoreCase(CHANGE_DELETE)) {
411: if (newObject instanceof AbstractChildObject) {
412: AbstractChildObject child = (AbstractChildObject) newObject;
413: m_path_cache.remove(child.getFullPath());
414: }
415: }
416: } catch (Exception e) {
417: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
418: }
419:
420: super .changeObject(cache_key, sChangeCode, newObject);
421: }
422:
423: /* (non-Javadoc)
424: * @see org.openharmonise.commons.cache.AbstractCache#clearCache()
425: */
426: public void clearCache() {
427: m_path_cache.clear();
428: super .clearCache();
429: }
430:
431: /* (non-Javadoc)
432: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectSaved(org.openharmonise.rm.resources.lifecycle.EditEvent)
433: */
434: public void workflowObjectSaved(EditEvent event) {
435: AbstractObject obj = (AbstractObject) event.getResult();
436: addToCache(String.valueOf(obj.getId()), obj);
437:
438: }
439:
440: /* (non-Javadoc)
441: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectStatusChanged(org.openharmonise.rm.resources.lifecycle.EditEvent)
442: */
443: public void workflowObjectStatusChanged(EditEvent event) {
444: AbstractObject obj = (AbstractObject) event.getSource();
445:
446: removeObjectFromCache(String.valueOf(obj.getId()));
447:
448: AbstractObject result = (AbstractObject) event.getResult();
449:
450: addToCache(String.valueOf(result.getId()), result);
451:
452: }
453:
454: /* (non-Javadoc)
455: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectArchived(org.openharmonise.rm.resources.lifecycle.EditEvent)
456: */
457: public void workflowObjectArchived(EditEvent event) {
458:
459: AbstractObject obj = (AbstractObject) event.getSource();
460:
461: removeObjectFromCache(String.valueOf(obj.getId()));
462: }
463:
464: /* (non-Javadoc)
465: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectReactivated(org.openharmonise.rm.resources.lifecycle.EditEvent)
466: */
467: public void workflowObjectReactivated(EditEvent event) {
468: AbstractObject obj = (AbstractObject) event.getResult();
469: addToCache(String.valueOf(obj.getId()), obj);
470:
471: }
472:
473: /* (non-Javadoc)
474: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectLocked(org.openharmonise.rm.resources.lifecycle.EditEvent)
475: */
476: public void workflowObjectLocked(EditEvent event) {
477: //do nothing
478:
479: }
480:
481: /* (non-Javadoc)
482: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectUnlocked(org.openharmonise.rm.resources.lifecycle.EditEvent)
483: */
484: public void workflowObjectUnlocked(EditEvent event) {
485: // do nothing
486:
487: }
488:
489: /**
490: * Returns an id for an object of the specified path.
491: *
492: * @param dbintrf the data store interface
493: * @param dsObj <code>DataStoreObject</code> which provides access to the relavent
494: * <code>ColumnRef</code> objects used in any DB queries
495: * @param sFullPath the path of the object
496: * @return an id for an object of the specified path
497: * @throws CacheException if an error occurs building the query from the
498: * parameters given
499: */
500: public static int getIdKeyFromPath(
501: AbstractDataStoreInterface dbintrf, DataStoreObject dsObj,
502: String sFullPath) throws CacheException {
503: int nKeyId = -1;
504: ResultSet rs = null;
505:
506: try {
507: SelectStatement select = new SelectStatement();
508:
509: int nIndex = sFullPath.lastIndexOf('/');
510: String sName = null;
511: String sPath = null;
512:
513: if (nIndex >= 0) {
514: sName = sFullPath.substring(nIndex + 1, sFullPath
515: .length());
516:
517: if (nIndex > 0) {
518: sPath = sFullPath.substring(0, nIndex);
519: }
520: } else {
521: sName = sFullPath;
522: }
523:
524: //System.out.println("path - " + sPath + ", name - " + sName);
525: select.addSelectColumn(dsObj.getInstanceColumnRef(
526: AbstractObject.ATTRIB_ID, false));
527:
528: ColumnRef status_col = dsObj.getInstanceColumnRef(
529: AbstractEditableObject.TAG_STATUS, false);
530: select.addSelectColumn(status_col);
531:
532: ColumnRef nameCol = dsObj.getInstanceColumnRef(
533: AbstractObject.TAG_NAME, false);
534: select.addSelectColumn(nameCol);
535:
536: if ((sPath != null) && (sPath.length() > 0)) {
537: ColumnRef pathCol = dsObj.getInstanceColumnRef(
538: AbstractChildObject.TAG_PATH, false);
539: select.addSelectColumn(pathCol);
540:
541: select.addWhereCondition(pathCol, "=", sPath);
542: }
543:
544: select.addWhereCondition(nameCol, "=", sName);
545:
546: select.addOrderBy(status_col,
547: SelectStatement.ORDER_ASCENDING);
548:
549: rs = dbintrf.execute(select);
550:
551: if (rs.next()) {
552: boolean bMatch = true;
553:
554: //perform match to catch case where DB isn't case sensitive
555: if (rs.getString(3).equals(sName) == false) {
556: if ((sPath != null) && (sPath.length() > 0)
557: && (rs.getString(4).equals(sPath) == false)) {
558: bMatch = false;
559: } else {
560: bMatch = false;
561: }
562: }
563:
564: if (bMatch == true) {
565: nKeyId = rs.getInt(1);
566: }
567: }
568: } catch (DataStoreException ds_e) {
569: throw new CacheException(
570: "Problem occured constructing query", ds_e);
571: } catch (SQLException sql_e) {
572: throw new CacheException(
573: "Problem occured processing query", sql_e);
574: } finally {
575: if (rs != null) {
576: try {
577:
578: rs.close();
579: } catch (SQLException sql_e) {
580: throw new CacheException(
581: "Problem closing result set", sql_e);
582: }
583: }
584: }
585:
586: return nKeyId;
587: }
588:
589: /* (non-Javadoc)
590: * @see org.openharmonise.commons.cache.AbstractCache#removeObjectFromCache(java.lang.Object)
591: */
592: public void removeObjectFromCache(Object cache_key) {
593: if (m_path_cache.containsValue(cache_key) == true) {
594: Iterator iter = m_path_cache.keySet().iterator();
595:
596: String removeKey = null;
597: boolean bFound = false;
598:
599: while (iter.hasNext() && (bFound == false)) {
600: String tempKey = (String) iter.next();
601:
602: if (m_path_cache.get(tempKey).equals(cache_key) == true) {
603: bFound = true;
604: removeKey = tempKey;
605: }
606: }
607:
608: if ((bFound == true) && (removeKey != null)) {
609: m_path_cache.remove(removeKey);
610: }
611: }
612:
613: super .removeObjectFromCache(cache_key);
614: }
615:
616: /* (non-Javadoc)
617: * @see org.openharmonise.commons.cache.AbstractCache#getCacheableObject(java.lang.Object)
618: */
619: protected Object getCacheableObject(Object object_id)
620: throws CacheException {
621: if (object_id == null) {
622: throw new IllegalArgumentException(m_sClassname
623: + " id must be passed "
624: + " to GetCacheableObject object_id:" + object_id);
625: }
626:
627: Object object = null;
628:
629: try {
630: int id = Integer.parseInt((String) object_id);
631: object = createObject(this .m_dsi, m_sClassname, Integer
632: .parseInt((String) object_id));
633: } catch (NumberFormatException e) {
634: object = createObject(this .m_dsi, m_sClassname,
635: (String) object_id);
636: }
637:
638: if (object == null) {
639: throw new RuntimeException("Could not get " + m_sClassname
640: + " for " + m_sClassname + ":" + object_id);
641: }
642:
643: if (object instanceof Editable) {
644: ((Editable) object).addEditEventListener(this );
645: }
646:
647: return object;
648: }
649:
650: /**
651: * Display path cache data in standard out.
652: *
653: */
654: private void printPathCache() {
655: Iterator iter = this .m_path_cache.values().iterator();
656:
657: while (iter.hasNext()) {
658: System.out.println((String) iter.next());
659: }
660: }
661:
662: /**
663: * Returns an id for an object of the given path.
664: *
665: * @param sFullPath the path of object
666: * @return an id for an object of the given path
667: * @throws CacheException if an error occurs
668: */
669: private String getIdKeyFromPath(String sFullPath)
670: throws CacheException {
671: String sKeyId = null;
672:
673: sKeyId = String.valueOf(getIdKeyFromPath(m_dsi,
674: (DataStoreObject) m_xobj_instance, sFullPath));
675:
676: return sKeyId;
677: }
678:
679: /* (non-Javadoc)
680: * @see org.openharmonise.commons.cache.AbstractCache#getObject(java.lang.Object)
681: */
682: public Object getObject(Object key) throws CacheException {
683: AbstractEditableObject child = (AbstractEditableObject) super
684: .getObject(key);
685:
686: if (child != null) {
687: child.addEditEventListener(this);
688: }
689:
690: return child;
691: }
692:
693: }
|