001/*
002 * Copyright (c) 2003-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2013-03-27 18:58:38 +0000 (Wed, 27 Mar 2013) $' 
007 * '$Revision: 31776 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.objectmanager.cache;
031
032import java.io.File;
033import java.io.FileInputStream;
034import java.io.FileNotFoundException;
035import java.io.FileOutputStream;
036import java.io.IOException;
037import java.io.ObjectInputStream;
038import java.io.ObjectOutputStream;
039import java.sql.Connection;
040import java.sql.Date;
041import java.sql.PreparedStatement;
042import java.sql.ResultSet;
043import java.sql.SQLException;
044import java.sql.Statement;
045import java.util.Collection;
046import java.util.Hashtable;
047import java.util.Iterator;
048import java.util.LinkedList;
049import java.util.List;
050import java.util.Vector;
051
052import org.apache.commons.logging.Log;
053import org.apache.commons.logging.LogFactory;
054import org.kepler.build.modules.ModuleTree;
055import org.kepler.kar.KAREntryHandler;
056import org.kepler.kar.KAREntryHandlerFactory;
057import org.kepler.objectmanager.lsid.KeplerLSID;
058import org.kepler.util.DotKeplerManager;
059import org.kepler.util.sql.DatabaseFactory;
060
061import ptolemy.actor.gui.Configuration;
062import ptolemy.util.MessageHandler;
063
064/**
065 * This class represents a disk cache of CacheObjects. The cache manages cache
066 * objects by calling their lifecycle event handlers and serializing every time
067 * a change is made. Objects in the cache each have a unique lsid. Once an
068 * object is in the cache, it will not be updated unless the lsid changes.
069 * 
070 * this class uses hsql to keep track of cache entries.
071 */
072public class CacheManager {
073
074        private static final Log log = LogFactory.getLog(CacheManager.class
075                        .getName());
076        private static final boolean isDebugging = log.isDebugEnabled();
077
078        private static CacheManager cache = null;
079
080        private Hashtable<String, CacheObjectInterface> objectHash = new Hashtable<String, CacheObjectInterface>();
081
082        private Vector<CacheListener> listeners = new Vector<CacheListener>();
083        private Vector<KAREntryHandler> karEntryHandlers = new Vector<KAREntryHandler>();
084
085        protected static final String cachePath = DotKeplerManager.getInstance()
086                        .getCacheDirString();
087        /** The directory containing serialized java objects in the cache. */
088    private static final String objectPath = cachePath + "objects"
089            + File.separator + getDatabaseSchemaName();
090        public static final String tmpPath = cachePath + "tmp";
091        public static final String CACHETABLENAME = "cacheContentTable";
092        public static final String CACHE_SEMTYPES_TABLE_NAME = "CACHE_SEMTYPES";
093
094        private Connection _conn;
095        private Statement _stmt;
096
097        private PreparedStatement _cacheInsertStatement;
098        private PreparedStatement _cacheSemTypesInsertStmt;
099        private PreparedStatement _cacheUpdateStatement;
100        private PreparedStatement _getLsidForLsidPrepStmt;
101        private PreparedStatement _getSemTypeForLsidPrepStmt;
102        private PreparedStatement _getLsidsForClassPrepStmt;
103
104        /**
105         * Construct a new CacheManager
106         */
107        protected CacheManager() throws CacheException {
108                if (isDebugging) 
109      log.debug("new CacheManager()");
110
111                try {
112                        _conn = DatabaseFactory.getDBConnection();
113                        _stmt = _conn.createStatement();
114                } catch (Exception e) {
115                        e.printStackTrace();
116                }
117
118                try {                   
119                    if (isDebugging) log.debug(countDB() + " Items Cached.");
120                    
121                        _cacheInsertStatement = _conn
122                                        .prepareStatement("insert into "
123                                                        + CACHETABLENAME
124                                                        + " (name, lsid, date, file, type, classname) values ( ?, ?, ?, ?, ?, ? )");
125                        _cacheSemTypesInsertStmt = _conn.prepareStatement("insert into "
126                                        + CACHE_SEMTYPES_TABLE_NAME
127                                        + " (LSID,SEMTYPE) values ( ?, ?)");
128                        _cacheUpdateStatement = _conn.prepareStatement("update "
129                                        + CACHETABLENAME + " set name = ?," // 1
130                                        + " date = ?," // 2
131                                        + " file = ?," // 3
132                                        + " type = ? " // 4
133                                        + " where lsid = ?"); // 5
134                        
135                        _getLsidForLsidPrepStmt = _conn.prepareStatement("select LSID from "
136                                + CACHETABLENAME + " where lsid = ?");
137
138                        _getSemTypeForLsidPrepStmt = _conn.prepareStatement("SELECT SEMTYPE FROM "
139                                + CACHE_SEMTYPES_TABLE_NAME + " WHERE LSID = ?");
140
141                        _getLsidsForClassPrepStmt = _conn.prepareStatement("select LSID from "
142                                + CACHETABLENAME + " where classname = ?");
143                        
144                } catch (SQLException e) {
145                        e.printStackTrace();
146                }
147                
148                // create the directory for the serialized java objects if it
149                // does not exist
150                File objectDir = new File(objectPath);
151                if(!objectDir.exists() && !objectDir.mkdirs()) {
152                    MessageHandler.error("Could not create directories " + objectDir);
153                }
154
155        }
156
157        /**
158         * create a new singleton instance of CacheManager
159         */
160        public static synchronized CacheManager getInstance() throws CacheException {
161                if (cache == null) {
162                        cache = new CacheManager();
163                        cache.initKAREntryHandlers();
164                }
165                return cache;
166        }
167        
168        /** Shutdown the cache manager. */
169        public static void shutdown() {
170            
171            if(cache != null) {
172                
173                cache.objectHash.clear();
174                cache.listeners.clear();
175                cache.karEntryHandlers.clear();
176
177                try {
178                    cache._stmt.close();
179                    cache._stmt = null;
180                    cache._conn.close();
181                    cache._conn = null;
182                } catch(SQLException e) {
183                    MessageHandler.error("Error closing database connection.", e);
184                }
185                
186                cache = null;
187            }
188        }
189
190        /**
191         * returns a temp file that is guaranteed to be around for one kepler
192         * session but could be deleted if the cache gets too full.
193         */
194        public synchronized File getTempFile() {
195                File f = new File(tmpPath);
196                f.mkdirs();
197                f = new File(tmpPath, "tmp" + System.currentTimeMillis());
198                // TODO: register this file somewhere so it can get deleted later
199                return f;
200        }
201
202        /**
203         * @param keh
204         */
205        public void addKAREntryHandler(KAREntryHandler keh) {
206                if (isDebugging)
207                        log.debug("addKAREntryHandler(" + keh.getClass().getName() + ")");
208                karEntryHandlers.add(keh);
209        }
210
211        public Collection<KAREntryHandler> getKAREntryHandlers() {
212                return karEntryHandlers;
213        }
214
215        /**
216         * Initialize all KAREntryHandlers that have been specified in
217         * configuration.
218         */
219        private void initKAREntryHandlers() {
220                if (isDebugging)
221                        log.debug("initKAREntryHandlers");
222
223                List configsList = Configuration.configurations();
224                Configuration config = null;
225                for (Iterator it = configsList.iterator(); it.hasNext();) {
226                        config = (Configuration) it.next();
227                        if (config != null) {
228                                break;
229                        }
230                }
231                if (config != null) {
232                        // KAREntryHandlerFactory KEHFactory = (KAREntryHandlerFactory)
233                        // config
234                        // .getAttribute("karEntryHandlerFactory");
235                        try {
236                                KAREntryHandlerFactory KEHFactory = new KAREntryHandlerFactory(
237                                                config, "KarEntryHandlerFactory");
238
239                                if (KEHFactory != null) {
240                                        boolean success = KEHFactory.registerKAREntryHandlers();
241                                        if (!success) {
242                                                System.out
243                                                                .println("error: karEntryHandlerFactory is null.  "
244                                                                                + "This "
245                                                                                + "problem can be fixed by adding a karEntryHandlerFactory "
246                                                                                + "property in the configuration.xml file.");
247                                        }
248                                } else {
249                                        System.out
250                                                        .println("error: KAREntryHandlerFactory is null.  This "
251                                                                        + "problem can be fixed by adding a karEntryHandlerFactory "
252                                                                        + "property in the configuration.xml file.");
253                                }
254                        } catch (ptolemy.kernel.util.NameDuplicationException nde) {
255                                // do nothing. the property is already there.
256                                System.out.println("#######################");
257                        } catch (Exception e) {
258                                System.out.println("Could not create KarEntryHandlerFactory: "
259                                                + e.getMessage());
260                                e.printStackTrace();
261                        }
262                }
263        }
264
265        /**
266         * insert a new CacheObjectInterface into the cache.
267         * 
268         * @param co
269         *            the cache object to insert
270         */
271        public synchronized void insertObject(CacheObjectInterface co)
272                        throws CacheException {
273                if (co == null)
274                        return;
275                // get the critical info
276                String name = co.getName();
277                KeplerLSID klsid = co.getLSID();
278                if (klsid == null) {
279                        log.warn("KAREntry has no lsid: " + name);
280                        return;
281                }
282                String lsid = klsid.toString();
283                String date = String.valueOf(System.currentTimeMillis());
284                String filename = co.getLSID().createFilename();
285                if (isDebugging)
286                        log.debug(name + " " + lsid + " " + filename);
287                if (isDebugging)
288                        log.debug(co.getClass().getName());
289
290                // save the entry to the DB
291                try {
292                        _cacheInsertStatement.clearParameters();
293                        _cacheInsertStatement.setString(1, name);
294                        _cacheInsertStatement.setString(2, lsid.toString());
295                        _cacheInsertStatement.setString(3, date);
296                        _cacheInsertStatement.setString(4, filename);
297                        _cacheInsertStatement.setString(5, co.getClass().getName());
298                        if (co instanceof ActorCacheObject) {
299                                String className = ((ActorCacheObject) co).getClassName();
300                                _cacheInsertStatement.setString(6, className);
301                        } else {
302                                _cacheInsertStatement.setNull(6, java.sql.Types.VARCHAR);
303                        }
304                        objectHash.put(lsid, co);
305                        serializeObjectInFile(co, filename);
306                        _cacheInsertStatement.executeUpdate();
307
308                        // insert the semantic types for this object
309                        if (co instanceof CacheObject) {
310                                Vector<String> semTypes = ((CacheObject) co).getSemanticTypes();
311                                for (String semType : semTypes) {
312                                        _cacheSemTypesInsertStmt.clearParameters();
313                                        _cacheSemTypesInsertStmt.setString(1, lsid.toString());
314                                        _cacheSemTypesInsertStmt.setString(2, semType);
315                                        _cacheSemTypesInsertStmt.executeUpdate();
316                                }
317                        }
318
319                        _conn.commit();
320                } catch (Exception sqle) {
321                        log.error(sqle.getMessage());
322                        try {
323                                _conn.rollback();
324                        } catch (Exception e) {
325                                throw new CacheException(
326                                                "Could not roll back the database after error "
327                                                                + sqle.getMessage(), e);
328                        }
329                        // sqle.printStackTrace();
330                        log
331                                        .error("Could not insert entry into cache: " + name + " "
332                                                        + lsid);
333                        throw new CacheException(
334                                        "Could not create hsql entry for new CacheObjectInterface: ",
335                                        sqle);
336                }
337                notifyListeners(co, "add");
338        }
339
340        /**
341         * update a CacheObjectInterface in the cache.
342         */
343        public synchronized void updateObject(CacheObjectInterface co)
344                        throws CacheException {
345                // get the critical info
346                String name = co.getName();
347                String lsid = co.getLSID().toString();
348                String date = String.valueOf(System.currentTimeMillis());
349                String filename = co.getLSID().createFilename();
350                String coType = co.getClass().getName();
351                // save the entry to the DB
352                try {
353                        _cacheUpdateStatement.setString(1, name);
354                        _cacheUpdateStatement.setString(2, date);
355                        _cacheUpdateStatement.setString(3, filename);
356                        _cacheUpdateStatement.setString(4, coType);
357                        _cacheUpdateStatement.setString(5, lsid.toString());
358                        _cacheUpdateStatement.executeUpdate();
359                        _cacheUpdateStatement.clearParameters();
360                        serializeObjectInFile(co, filename);
361                        _conn.commit();
362                } catch (Exception sqle) {
363                        try {
364                                _conn.rollback();
365                        } catch (Exception e) {
366                                throw new CacheException(
367                                                "Could not roll back the database after error "
368                                                                + sqle.getMessage(), e);
369                        }
370                        throw new CacheException(
371                                        "Could not create hsql entry for new CacheObjectInterface: ",
372                                        sqle);
373                }
374                notifyListeners(co, "update");
375        }
376
377        /**
378         * remove the CacheObjectInterface with the specified lsid.
379         * 
380         * @param lsid
381         */
382        public void removeObject(KeplerLSID lsid) throws CacheException {
383                // grab a copy from the hash to use for calling listeners. There will be
384                // no listeners on an object returned from the db.
385                CacheObjectInterface co = (CacheObjectInterface) objectHash.get(lsid
386                                .toString());
387                try {
388                        String sql = "SELECT file FROM " + CACHETABLENAME + " WHERE lsid='"
389                                        + lsid.toString() + "'";
390                        if (isDebugging)
391                                log.debug(sql);
392                        ResultSet rs = _stmt.executeQuery(sql);
393                        if (rs == null)
394                                throw new SQLException("Query Failed: " + sql);
395                        if (rs.next()) {
396                                File f = new File(objectPath, rs.getString("file"));
397                                if (isDebugging)
398                                        log.debug(f.toString());
399                                if (f.exists()) {
400                                        f.delete();
401                                }
402                        }
403                        rs.close();
404
405                        try {
406                                sql = "DELETE FROM " + CACHETABLENAME + " WHERE lsid='"
407                                                + lsid.toString() + "'";
408                                if (isDebugging) {
409                                        // log.debug(showDB());
410                                        log.debug(sql);
411                                }
412                                _stmt.execute(sql);
413                        } catch (Exception e) {
414                                log.error(lsid.toString() + " did not exist in "
415                                                + CACHETABLENAME);
416                                log.error(e.getMessage());
417                        }
418                        _conn.commit();
419                        objectHash.remove(lsid.toString());
420                } catch (Exception e) {
421                        try {
422                                _conn.rollback();
423                        } catch (Exception e1) {
424                                throw new CacheException(
425                                                "Could not roll back the database after error "
426                                                                + e.getMessage(), e1);
427                        }
428                        throw new CacheException("Error removing object " + lsid
429                                        + " from the cache", e);
430                }
431                if (co != null) {
432                        notifyListeners(co, "remove");
433                }
434        }
435
436        /**
437         * debug method to show the contents of the table
438         * 
439         * @throws CacheException, SQLException
440         */
441        public String showDB() throws CacheException, SQLException {
442                StringBuilder buf = new StringBuilder();
443                PreparedStatement selectAll;
444                // Create the prepared statement.
445                try {
446                        // FIXME reuse this prepared statement
447                        selectAll = _conn
448                                        .prepareStatement("select name, lsid, file, type from "
449                                                        + CACHETABLENAME);
450                } catch (SQLException e) {
451                        e.printStackTrace();
452                        throw new CacheException("Unable to create prepared statement.");
453                }
454                try {
455                        ResultSet rs = selectAll.executeQuery();
456                        while (rs.next()) {
457                                // name, lsid, file, type
458                                String name = rs.getString("name");
459                                String lsid = rs.getString("lsid");
460                                String file = rs.getString("file");
461                                String coType = rs.getString("type");
462                                buf.append("name: ");
463                                buf.append(name);
464                                buf.append("  lsid: ");
465                                buf.append(lsid);
466                                buf.append("  file: ");
467                                buf.append(file);
468                                buf.append("  type: ");
469                                buf.append(coType);
470                                buf.append("\n");
471                        }
472                } catch (Exception e) {
473                        System.out.println("error showing db: " + e.getMessage());
474                } finally{
475                        selectAll.close();
476                }
477                return buf.toString();
478        }
479
480        /**
481         * debug method to count the contents of the table * @throws CacheException
482         */
483        public int countDB() throws CacheException, SQLException {
484                PreparedStatement selectAll = null;
485                // Create the prepared statements.
486                try {
487                        selectAll = _conn.prepareStatement("select lsid from "
488                                        + CACHETABLENAME);
489                } catch (SQLException e) {
490                        e.printStackTrace();
491                        throw new CacheException("Unable to create prepared statements.");
492                }
493                int cnt = 0;
494                try {
495                        ResultSet rs = selectAll.executeQuery();
496                        while (rs.next()) {
497                                cnt++;
498                        }
499                } catch (Exception e) {
500                        System.out.println("error counting db: " + e.getMessage());
501                } finally{
502                        selectAll.close();
503                }
504                return cnt;
505
506        }
507
508        /**
509         * return the CacheObjectInterface with the specified lsid. Returns null if
510         * the object is not in the cache and cannot be resolved.
511         * 
512         * @param lsid
513         * @throws CacheException
514         *             when there is an issue with the cache.
515         */
516        public CacheObjectInterface getObject(KeplerLSID lsid)
517                        throws CacheException {
518                if (isDebugging)
519                        log.debug(lsid.toString());
520                // first look in the hash table
521                CacheObjectInterface co = (CacheObjectInterface) objectHash.get(lsid
522                                .toString());
523
524                // Found it in the hash - return it.
525                if (co != null) {
526                        /*
527                         * using if (isDebugging) here is bad since it cause getObject to be called
528                         * when it is not supposed to, thus invoking the momlparser
529                                log.debug("    was found in the hash");
530                                if (co.getObject() != null) {
531                                        if (co.getObject() instanceof NamedObj) {
532                                                log.debug("NamedObj: "
533                                                                + ((NamedObj) co.getObject()).getName());
534                                        } else {
535                                                log.debug("Object: "
536                                                                + co.getObject().getClass().getName());
537                                        }
538                                } else {
539                                        log
540                                                        .debug("the object contained by this cache object is NULL");
541                                }
542                        */
543                        return co;
544                }
545
546                // Now look in the database:
547                try {
548                        String query = "select name, lsid, file from " + CACHETABLENAME
549                                        + " where lsid='" + lsid.toString() + "'";
550                        ResultSet rs = null;
551                        try {
552                                rs = _stmt.executeQuery(query);
553                                if (rs == null)
554                                        throw new SQLException("Query Failed: " + query);
555                                if (rs.next()) {
556                                        // found it in the database.
557                                        String name = rs.getString("name");
558                                        String file = rs.getString("file");
559                                        if (isDebugging)
560                                                log.debug(name + " " + file);
561                                        File theObjectFile = new File(objectPath, file);
562                                        if (isDebugging)
563                                                log.debug(theObjectFile.toString());
564                                        FileInputStream fileInputStream = null;
565                                        ObjectInputStream ois = null;
566                                        try {
567                                                fileInputStream = new FileInputStream(theObjectFile);
568                                            ois = new ObjectInputStream(fileInputStream);
569                                        // deserialize the CacheObjectInterface
570                                        co = (CacheObjectInterface) ois.readObject();
571                                        co.setName(name);
572                                        co.setLSID(lsid);
573                                        // add the CacheObjectInterface to the hashtable for easier
574                                        // access next time
575                                        objectHash.put(lsid.toString(), co);
576                                        return co;
577                                        } finally {
578                                            if(ois != null) {
579                                                ois.close();
580                                            }
581                                            if(fileInputStream != null) {
582                                                fileInputStream.close();
583                                            }
584                                        }
585                                }
586                        } finally {
587                                if(rs != null) {
588                                        rs.close();
589                                }
590                        }
591                } catch (SQLException sqle) {
592                        sqle.printStackTrace();
593                        throw new CacheException("SQL exception when getting object", sqle);
594                } catch (Exception e) {
595                        throw new CacheException(
596                                        "Exception occurred while deserializing object", e);
597                }
598                return co;
599        }
600
601        public Vector<CacheContent> getCachedContents() {
602                return getCachedContents("");
603        }
604
605        /**
606         * @param type
607         * @return
608         */
609        public Vector<CacheContent> getCachedContents(String type) {
610
611                Vector<CacheContent> contents = new Vector<CacheContent>();
612
613                String query = "SELECT NAME,LSID,DATE,FILE,TYPE,CLASSNAME FROM "
614                                + CACHETABLENAME;
615                if (!type.trim().equals("")) {
616                        query += " WHERE TYPE = '" + type + "'";
617                }
618                try {
619                        ResultSet rs = null;
620                        try {
621                                rs = _stmt.executeQuery(query);
622                                if (rs == null)
623                                        throw new SQLException("Query Failed: " + query);
624                                while (rs.next()) {
625                                        CacheContent cc = new CacheContent();
626                                        cc.setName(rs.getString(1));
627                                        try {
628                                                KeplerLSID lsid = new KeplerLSID(rs.getString(2));
629                                                cc.setLsid(lsid);
630                                                Long l = Long.parseLong(rs.getString(3));
631                                                Date d = new Date(l);
632                                                cc.setDateChanged(d);
633                                                File f = new File(rs.getString(4));
634                                                cc.setFile(f);
635                                                cc.setType(rs.getString(5));
636                                                cc.setClassName(rs.getString(6));
637                                                contents.add(cc);
638                                        } catch (Exception e) {
639                                                e.printStackTrace();
640                                        }
641                                }
642                        } finally {
643                                if(rs != null) {
644                                        rs.close();
645                                }
646                        }
647                } catch (SQLException e1) {
648                        e1.printStackTrace();
649                        contents = new Vector<CacheContent>();
650                }
651
652                return contents;
653
654        }
655
656        /**
657         * Return a complete list of KeplerLSIDs for everything that is in the
658         * cache.
659         * 
660         * @return
661         */
662        public Vector<KeplerLSID> getCachedLsids() {
663                return getCachedLsids(null);
664        }
665        
666        /**
667         * @param lsid
668         * @return
669         */
670        public Vector<KeplerLSID> getCachedLsids(KeplerLSID lsid) {
671                Vector<KeplerLSID> lsids = new Vector<KeplerLSID>();
672
673                String query = "SELECT LSID FROM " + CACHETABLENAME;
674                if (lsid != null) {
675                        query += " WHERE LSID like '" + lsid.toStringWithoutRevision() + "%'";
676                }
677                if (isDebugging) log.debug(query);
678                ResultSet rs;
679                try {
680                        rs = _stmt.executeQuery(query);
681                        if (rs == null)
682                                throw new SQLException("Query Failed: " + query);
683                        while (rs.next()) {
684                                String lsidStr = rs.getString(1);
685                                try {
686                                        KeplerLSID cachedLsid = new KeplerLSID(lsidStr);
687                                        lsids.add(cachedLsid);
688                                } catch (Exception e) {
689                                        e.printStackTrace();
690                                }
691                        }
692                        rs.close();
693                } catch (SQLException e1) {
694                        e1.printStackTrace();
695                        lsids = new Vector<KeplerLSID>();
696                }
697                
698                return lsids;
699        }
700        
701        /** Get a list of LSIDs for a class name. */
702        public List<KeplerLSID> getCachedLsidsForClass(String className) throws Exception {
703            List<KeplerLSID> retval = new LinkedList<KeplerLSID>();
704            ResultSet result = null;
705        try {
706            _getLsidsForClassPrepStmt.setString(1, className);
707            result = _getLsidsForClassPrepStmt.executeQuery();
708            while (result.next()) {
709                retval.add(new KeplerLSID(result.getString(1)));
710            }
711        } finally {
712            if (result != null) {
713                result.close();
714            }
715        }
716            return retval;
717        }
718
719        /**
720         * @param lsid
721         * @return
722         */
723        public Long getHighestCachedLsidRevision(KeplerLSID lsid) {
724                Long highestRev = 0L;
725                Vector<KeplerLSID> cachedLsids = getCachedLsids(lsid);
726                for (KeplerLSID cachedLsid : cachedLsids) {
727                        Long thisRev = cachedLsid.getRevision();
728                        if (thisRev > highestRev) {
729                                highestRev = thisRev;
730                        }
731                }
732                return highestRev;
733        }
734
735        /**
736         * Return a list of Semantic Types for the given LSID.
737         * 
738         * @param lsid
739         * @return
740         */
741        public Vector<KeplerLSID> getSemanticTypesFor(KeplerLSID lsid) {
742
743                Vector<KeplerLSID> semTypes = new Vector<KeplerLSID>();
744
745                ResultSet rs;
746                try {
747                _getSemTypeForLsidPrepStmt.setString(1, lsid.toString());
748                        rs = _getSemTypeForLsidPrepStmt.executeQuery();
749                        if (rs == null)
750                                throw new SQLException("Query Failed: " + _getSemTypeForLsidPrepStmt);
751                        while (rs.next()) {
752                                String lsidStr = rs.getString(1);
753                                try {
754                                        KeplerLSID semType = new KeplerLSID(lsidStr);
755                                        semTypes.add(semType);
756                                } catch (Exception e) {
757                                        e.printStackTrace();
758                                }
759                        }
760                        rs.close();
761                } catch (SQLException e1) {
762                        e1.printStackTrace();
763                        semTypes = new Vector<KeplerLSID>();
764                }
765
766                return semTypes;
767
768        }
769
770        /**
771         * return an iterator of all of the CacheObjectInterfaces in the cache
772         */
773        public Iterator<CacheObjectInterface> getCacheObjectIterator()
774                        throws CacheException {
775                return getCacheObjectIterator("");
776        }
777
778        /**
779         * Return the CacheObject that has the highest revision number and matches
780         * the given LSID, return null if not found.
781         * 
782         * @param anLsid
783         * @return
784         * @throws CacheException
785         */
786        public CacheObject getHighestCacheObjectRevision(KeplerLSID anLsid)
787                        throws CacheException {
788
789                CacheObject coWithHighestLSID = null;
790                try {
791                        long highestRev = 0;
792                        Vector<KeplerLSID> cachedLsids = getCachedLsids();
793                        for (KeplerLSID lsid : cachedLsids) {
794                                if (lsid.equalsWithoutRevision(anLsid)) {
795                                        if (lsid.getRevision() > highestRev) {
796                                                highestRev = lsid.getRevision();
797                                        }
798                                }
799                        }
800
801                        String highestRevLsidStr = anLsid.toStringWithoutRevision() + ":"
802                                        + highestRev;
803                        KeplerLSID highestRevLsid = new KeplerLSID(highestRevLsidStr);
804
805                        coWithHighestLSID = (CacheObject) getObject(highestRevLsid);
806                } catch (Exception e) {
807                        log.error(e.getMessage());
808                        e.printStackTrace();
809                }
810                return coWithHighestLSID;
811        }
812
813        /**
814         * Read the Java Object Serialized in the given file and add it to the
815         * objectHash.
816         * 
817         * @param file
818         * @return
819         * @throws Exception
820         */
821        private CacheObjectInterface deserializeCacheObject(String file)
822                        throws Exception {
823                // deserialize the cache object and put it in the hash
824                File f = new File(objectPath, file);
825                try {
826                        FileInputStream fis = new FileInputStream(f);
827                        ObjectInputStream ois = null;
828                        try {
829                            ois = new ObjectInputStream(fis);
830                        CacheObjectInterface coi = null;
831                        coi = (CacheObjectInterface) ois.readObject();
832                        if (coi != null) {
833                                // TODO do all items need to be added to objectHash here?
834                                // for most cacheObjects getLSID() returns null, which was
835                                // giving an NPE here.
836                                // ActorCacheObject seems to work because it implements
837                                // readExternal which
838                                // sets its lsid.
839                                if (coi.getLSID() != null) {
840                                        objectHash.put(coi.getLSID().toString(), coi);
841                                }
842                                return coi;
843                        }
844                        } finally {
845                            if(ois != null) {
846                                ois.close();
847                            }
848                        }
849                } catch (FileNotFoundException fnfe) {
850                        log.warn(file + " could not be found:" + fnfe);
851                } catch (ClassNotFoundException cnfe) {
852                        log.warn(file + " could not be instantiated:");
853                        log.warn("  No class definition found for " + cnfe.getMessage());
854                }
855                return null;
856        }
857
858        /**
859         * @param type
860         * @return all cache objects that are of the specified type
861         * @throws CacheException
862         */
863        public Iterator<CacheObjectInterface> getCacheObjectIterator(String type)
864                        throws CacheException {
865                if (isDebugging)
866                        log.debug("getCacheObjectIterator(" + type + ")");
867                try {
868                        Vector<CacheObjectInterface> items = new Vector<CacheObjectInterface>();
869                        String sql = "select name, lsid, file, type from " + CACHETABLENAME;
870                        if (!type.trim().equals("")) {
871                                sql += " WHERE type = '" + type + "'";
872                        }
873                        if (isDebugging)
874                                log.debug(sql);
875                        ResultSet rs = _stmt.executeQuery(sql);
876                        while (rs.next()) {
877                                String name = rs.getString("name");
878                                String file = rs.getString("file");
879                                String lsidString = rs.getString("lsid");
880                                String coType = rs.getString("type");
881                                if (isDebugging) {
882                                        log.debug("name: " + name);
883                                        log.debug("file: " + file);
884                                        log.debug("lsidString: " + lsidString);
885                                        log.debug("type: " + coType);
886                                }
887
888                                CacheObjectInterface coi = (CacheObjectInterface) objectHash
889                                                .get(lsidString);
890                                if (coi == null) {
891                                        deserializeCacheObject(file);
892                                }
893                                items.add(coi);
894                        }
895                        rs.close();
896                        return items.iterator();
897                } catch (Exception e) {
898                        e.printStackTrace();
899                        throw new CacheException(
900                                        "Error creating CacheObjectInterface iterator. "
901                                                        + "Try removing the ~/.kepler directory?: "
902                                                        + e.getMessage());
903                }
904        }
905
906        /**
907         * return true of the given lsid has an associated object in the cache
908         * 
909         * @param lsid
910         */
911        public boolean isContained(KeplerLSID lsid) throws CacheException {
912                boolean foundIt = false;
913                try {
914                    _getLsidForLsidPrepStmt.setString(1, lsid.toString());
915                        ResultSet rs = _getLsidForLsidPrepStmt.executeQuery();
916                        if (rs.next()) {
917                                foundIt = true;
918                        }
919                        rs.close();
920                } catch (Exception e) {
921                        throw new CacheException("Error determining contents of cache: "
922                                        + e.getMessage());
923                }
924                
925                /*
926                if(foundIt && !_isObjectSerializedInFile(lsid)) {
927                    System.out.println("WARNING: in cache database, but not on file system: " + lsid);
928                }
929                */
930                
931                return foundIt;
932        }
933
934        /**
935         * clear the cache of all contents
936         */
937        public void clearCache() throws SQLException, CacheException {
938                // Remove the data files.
939                CacheUtil.cleanUpDir(new File(objectPath));
940                // Clear our objectHash.
941                objectHash.clear();
942
943                String sql = "delete from " + CACHETABLENAME;
944                _stmt.execute(sql);
945                _conn.commit();
946
947        }
948
949        /**
950         * add a CacheListener to listen for cache events
951         */
952        public void addCacheListener(CacheListener listener) {
953                listeners.add(listener);
954        }
955
956        /**
957         * notifies any listeners as to an action taking place.
958         */
959        private void notifyListeners(CacheObjectInterface co, String op) {
960                CacheEvent ce = new CacheEvent(co);
961
962                if (op.equals("add")) {
963                        for (int i = 0; i < listeners.size(); i++) {
964                                CacheListener cl = (CacheListener) listeners.elementAt(i);
965                                cl.objectAdded(ce);
966                        }
967                        co.objectAdded();
968                } else if (op.equals("remove")) {
969                        for (int i = 0; i < listeners.size(); i++) {
970                                CacheListener cl = (CacheListener) listeners.elementAt(i);
971                                cl.objectRemoved(ce);
972                        }
973                        co.objectRemoved();
974                } else if (op.equals("purge")) {
975                        for (int i = 0; i < listeners.size(); i++) {
976                                CacheListener cl = (CacheListener) listeners.elementAt(i);
977                                cl.objectPurged(ce);
978                        }
979                        co.objectPurged();
980                }
981        }
982
983        private void serializeObjectInFile(CacheObjectInterface co, String filename)
984                        throws CacheException {
985                File outputFile = new File("");
986                try {
987                        outputFile = new File(CacheManager.objectPath, filename);
988                        if (isDebugging)
989                                log.debug(outputFile.toString());
990                        FileOutputStream fos = new FileOutputStream(outputFile);
991                        ObjectOutputStream oos = new ObjectOutputStream(fos);
992                        oos.writeObject(co); // serialize the CacheObjectInterface itself
993                        oos.flush();
994                        oos.close();
995                } catch (IOException e) {
996                        log.error("Serializing object to cache has failed: "+outputFile.toString());
997                        e.printStackTrace();
998                        throw new CacheException("Unable to serialize " + co.getName(), e);
999                }
1000
1001        }
1002        
1003        /** Returns true if the serialized object for an LSID exists
1004         *  in cache objects directory.
1005         */
1006        private boolean _isObjectSerializedInFile(KeplerLSID lsid) {
1007            File file = new File(CacheManager.objectPath, lsid.toString());
1008            return file.exists() && file.isFile();
1009        }
1010        
1011        /** Get the name of the cache database schema. */
1012        public static String getDatabaseSchemaName() {
1013        // set the schema based on the the contents of the suite.
1014            try {
1015            // put "S" in front of the schema name: HSQL will not accept
1016            // schema names beginning with a digit.
1017                return "S" + ModuleTree.instance().getModuleConfigurationMD5();
1018            } catch(Exception e) {
1019                MessageHandler.error("Error getting cache database schema name; using PUBLIC instead.", e);
1020                return "PUBLIC";
1021            }
1022        }
1023}