001/*
002 * Copyright (c) 2009-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2014-08-13 22:56:50 +0000 (Wed, 13 Aug 2014) $' 
007 * '$Revision: 32878 $'
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.InputStream;
038import java.io.ObjectInput;
039import java.io.ObjectInputStream;
040import java.io.ObjectOutputStream;
041import java.io.OutputStream;
042import java.sql.Connection;
043import java.sql.PreparedStatement;
044import java.sql.ResultSet;
045import java.sql.SQLException;
046import java.sql.Statement;
047import java.util.Arrays;
048import java.util.Enumeration;
049import java.util.Iterator;
050import java.util.LinkedHashMap;
051import java.util.LinkedList;
052import java.util.List;
053import java.util.Map;
054import java.util.StringTokenizer;
055import java.util.Vector;
056
057import javax.swing.tree.DefaultMutableTreeNode;
058import javax.swing.tree.DefaultTreeModel;
059import javax.swing.tree.TreeModel;
060
061import org.apache.commons.logging.Log;
062import org.apache.commons.logging.LogFactory;
063import org.kepler.build.modules.Module;
064import org.kepler.build.modules.ModuleTree;
065import org.kepler.configuration.ConfigurationManager;
066import org.kepler.configuration.ConfigurationProperty;
067import org.kepler.sms.NamedOntModel;
068import org.kepler.sms.OntologyCatalog;
069import org.kepler.util.DotKeplerManager;
070import org.kepler.util.FileUtil;
071import org.kepler.util.StatusNotifier;
072import org.kepler.util.sql.DatabaseFactory;
073
074import ptolemy.util.MessageHandler;
075
076public class LocalRepositoryManager {
077        private static final Log log = LogFactory
078                        .getLog(LocalRepositoryManager.class.getName());
079        private static final boolean isDebugging = log.isDebugEnabled();
080
081        public static final String KAR_LOCAL_REPOS_TABLE_NAME = "KAR_LOCAL_REPOS";
082
083        /** The name in the configuration file containing the display name
084         *  for modules.
085         */
086        private static final String MODULE_DISPLAY_NAME = "moduleDisplayName";
087        
088        /**
089         * The list of KAR files that make up the library.
090         */
091        private Vector<File> _karFiles;
092        
093        /** List of MoML files that make up the library. */
094        private Vector<File> _xmlFiles;
095
096        private Connection _conn;
097        private Statement _stmt;
098        private PreparedStatement _insertPrepStmt;
099        private PreparedStatement _deletePrepStmt;
100        private PreparedStatement _updateNamePrepStmt;
101        private PreparedStatement _updatePathPrepStmt;
102        
103        private LinkedHashMap<String, TreeModel> _folderModel;
104
105        public TreeModel getFolderModel(String name) {
106                return _folderModel.get(name);
107        }
108
109        public void setFolderModel(LinkedHashMap<String, TreeModel> folderModel) {
110                _folderModel = folderModel;
111        }
112
113        /**
114         * The local repositories that the cache is built from. Keys are the
115         * repository names, values are the directory files.
116         */
117        private LinkedHashMap<LocalRepository, String> _localRepositories;
118
119        /**
120         * This is the folder that we save KARs to by default.
121         */
122        private LocalRepository _localSaveRepo;
123
124        /**
125         * The file in the module readwrite area where we'll save the name of the
126         * user selected local repository to save KAR files in by default.
127         */
128        private String _localSaveRepoFileName;
129
130        /**
131         * We keep a copy of the initial local repositories so we can see if they
132         * have changed.
133         */
134        private LinkedHashMap<LocalRepository, String> _checkpointRepos;
135
136        private File _defaultUserWorkflowDirectory;
137
138        /**
139         * Empty Constructor.
140         */
141        public LocalRepositoryManager() {
142                
143                DotKeplerManager dkm = DotKeplerManager.getInstance();
144
145                // Set up file name for storing default local save directory
146                File modDir = dkm.getTransientModuleDirectory("core");
147                if (modDir != null) {
148                        _localSaveRepoFileName = modDir.toString();
149                } else {
150                        _localSaveRepoFileName = System.getProperty("KEPLER");
151                }
152                if (!_localSaveRepoFileName.endsWith(File.separator)) {
153                        _localSaveRepoFileName += File.separator;
154                }
155                _localSaveRepoFileName += "LocalSaveRepository";
156
157                // Set up the location of the default workflows directory
158                _defaultUserWorkflowDirectory = dkm.getPersistentUserWorkflowsDir();//new File(persistentDir, "workflows");
159                if (!_defaultUserWorkflowDirectory.exists()) {
160                        _defaultUserWorkflowDirectory.mkdirs();
161                }
162
163                // Set up prepared statements for select,insert,update,delete
164                // local repository information
165                try {
166                        _conn = DatabaseFactory.getDBConnection();
167                } catch (Exception e) {
168                    MessageHandler.error("Error opening cache database.", e);
169                    return;
170                }
171
172                try {
173                        _stmt = _conn.createStatement();
174                        _insertPrepStmt = _conn.prepareStatement("insert into "
175                                        + KAR_LOCAL_REPOS_TABLE_NAME
176                                        + " (name,path) values ( ?, ? ) ");
177                        _deletePrepStmt = _conn.prepareStatement("DELETE FROM "
178                                        + KAR_LOCAL_REPOS_TABLE_NAME + " WHERE PATH = ? ");
179                        _updateNamePrepStmt = _conn.prepareStatement("UPDATE "
180                                        + KAR_LOCAL_REPOS_TABLE_NAME
181                                        + " SET NAME = ? WHERE PATH = ? ");
182                        _updatePathPrepStmt = _conn.prepareStatement("UPDATE "
183                                + KAR_LOCAL_REPOS_TABLE_NAME
184                                + " SET PATH = ? WHERE NAME = ? ");
185
186                } catch (SQLException e) {
187                        e.printStackTrace();
188                }
189
190                initLocalRepos();
191                initLocalSaveRepo();
192        }
193
194        /**
195         * Initialize local repositories that contain KAR files.
196         */
197        private void initLocalRepos() {
198
199                // Check to see if there are any local repositories
200                try {
201                        String query = "SELECT count(*) FROM " + KAR_LOCAL_REPOS_TABLE_NAME;
202                        if (isDebugging)
203                                log.debug(query);
204                        ResultSet rs = null;
205                        try {
206                                rs = _stmt.executeQuery(query);
207                                if (rs != null && rs.next()) {
208                                        int cnt = rs.getInt(1);
209                                        if (cnt <= 0) {
210                                                // Set the defaults if there are no local repositories in
211                                                // the database
212                                                this.setDefaultLocalRepos();
213                                        }
214                                }
215                        } finally {
216                                if(rs != null) {
217                                        rs.close();
218                                }
219                        }
220                } catch (SQLException sqle) {
221                        log.error(sqle.getMessage());
222                }
223
224                refreshReposFromDB();
225
226        }
227        
228        public LinkedHashMap<LocalRepository, String> selectReposFromDB() {
229                LinkedHashMap<LocalRepository, String> localRepos = new LinkedHashMap<LocalRepository, String>();
230                try {
231                        String query = "SELECT name,path FROM "
232                                        + KAR_LOCAL_REPOS_TABLE_NAME + " order by name";
233                        if (isDebugging)
234                                log.debug(query);
235                        ResultSet rs = null;
236                        try {
237                                rs = _stmt.executeQuery(query);
238                                if (rs != null) {
239                                        while (rs.next()) {
240                                                String theName = rs.getString(1);
241                                                String paths = rs.getString(2);
242                                                LocalRepository repo = new LocalRepository(paths);
243                                                localRepos.put(repo, theName);
244                                        }
245                                }
246                        } finally {
247                                if(rs != null) {
248                                        rs.close();
249                                }
250                        }
251                } catch (SQLException sqle) {
252                        log.error(sqle.getMessage());
253                        sqle.printStackTrace();
254                }
255                return localRepos;
256        }
257
258        /**
259         * Repopulate our local hashtable from the database.
260         */
261        private void refreshReposFromDB() {
262                _localRepositories = selectReposFromDB();
263        }
264
265        /**
266         * Return a list of all the KAR files that were found after calling
267         * scanReposForKarFiles()
268         * 
269         * @return Vector of File objects pointing to KAR files
270         */
271        public Vector<File> getKarFiles() {
272                return _karFiles;
273        }
274        
275        /** Return a list of all the XML files that were found after calling
276         *  scanReposForXMLFiles()
277         */
278        public Vector<File> getXMLFiles() {
279            //return new Vector<File>();
280            return _xmlFiles;
281        }
282
283        /**
284         * Search for Kar files in local Kar Repositories and build a list of all
285         * the KAR files that are found. This list can be retrieved using
286         * getKarFiles()
287         */
288        public void scanReposForKarFiles() {
289                StatusNotifier.log("Scanning Local Repositories for KAR files.");
290                _karFiles = new Vector<File>();
291                _xmlFiles = new Vector<File>();
292                
293                LinkedHashMap<String, TreeModel> folderModel = new LinkedHashMap<String, TreeModel>();
294                setFolderModel(folderModel);
295
296                for (LocalRepository repoRoot : getLocalRepositories().keySet()) {
297                        if (isDebugging) {
298                                log.debug("Recursing for Kar files in local repository: "
299                                                + repoRoot.toString());
300                        }
301                        refreshFolderModelForRepo(repoRoot);
302                }
303        }
304
305        /**
306         * Refresh the folder model for the specified local repository.
307         * 
308         * @param repo
309         */
310        public void refreshFolderModelForRepo(LocalRepository repo) {
311                if (isDebugging) log.debug("refreshFolderModelForRepo("+repo.toString()+")");
312
313                String repoName = getLocalRepositories().get(repo);
314                if (repoName == null) {
315                        log.warn("Error: not a local repository: " + repo);
316                        return;
317                }
318
319                TreeModel tm = getFolderModel(repoName);
320                if (tm == null) {
321                        tm = new DefaultTreeModel(new DefaultMutableTreeNode(repo));
322                        _folderModel.put(repoName, tm);
323                }
324
325                DefaultMutableTreeNode root = (DefaultMutableTreeNode) tm.getRoot();
326                root.removeAllChildren();
327
328                for(File dir : repo.getDirectories()) {
329                    findKarsRecursive(dir, 20, root);
330                }               
331        }
332        
333        /**
334         * Given the File object for a folder in a local repository, return the
335         * corresponding DefaultMutableTreeNode object from the Folder model.
336         * 
337         * @param folder
338         * @return
339         */
340        public DefaultMutableTreeNode getFolderModelNode(File folder) {
341                String folderStr = folder.toString();
342                for (LocalRepository repo : getLocalRepositories().keySet()) {
343                    for(File repoRootDir : repo.getDirectories()) {
344                        if (folderStr.equals(repoRootDir.toString())) {
345                                return (DefaultMutableTreeNode) getFolderModel(
346                                                getLocalRepositories().get(repo)).getRoot();
347                        } else if (folderStr.startsWith(repoRootDir.toString())) {
348                                String remainder = folderStr.substring(repoRootDir.toString().length());
349                                if (remainder.startsWith(File.separator)) {
350                                        remainder = remainder.substring(1);
351                                }
352                                StringTokenizer st = new StringTokenizer(remainder,
353                                                File.separator);
354    
355                                TreeModel tm = getFolderModel(getLocalRepositories().get(repo));
356                                DefaultMutableTreeNode root = (DefaultMutableTreeNode) tm
357                                                .getRoot();
358                                String dir = null;
359                                File current = repoRootDir;
360                                int count = st.countTokens();
361                                while (st.hasMoreTokens()) {
362                                        dir = st.nextToken();
363                                        current = new File(current.toString(), dir);
364                                        DefaultMutableTreeNode dmtn = checkChildren(root, current);
365                                        if (dmtn == null) {
366                                                return null;
367                                        }
368                                        if (count == 1) {
369                                                return dmtn;
370                                        } else {
371                                                root = dmtn;
372                                        }
373                                        count--;
374                                }
375                        }
376                    }
377                }
378
379                return null;
380        }
381
382        /**
383         * 
384         * @param dmtn
385         * @param dir
386         * @return the TreeNode that corresponds to the given directory name
387         */
388        private DefaultMutableTreeNode checkChildren(DefaultMutableTreeNode dmtn,
389                        File folder) {
390
391                Enumeration<?> children = dmtn.children();
392                while (children.hasMoreElements()) {
393                        DefaultMutableTreeNode child = (DefaultMutableTreeNode) children
394                                        .nextElement();
395                        if (child.getUserObject().equals(folder)) {
396                                return child;
397                        }
398
399                }
400                return null;
401        }
402
403        public void refreshFolderModelForFolder(File folder) {
404
405                // find the corresponding TreeNode
406                DefaultMutableTreeNode dmtn = getFolderModelNode(folder);
407                if (dmtn == null) {
408                        return;
409                }
410
411                // remove the children of that TreeNode
412                dmtn.removeAllChildren();
413
414                // rebuild the children of the TreeNode
415                findKarsRecursive(folder, 20, dmtn);
416
417        }
418
419        /**
420         * Recursive function for finding files that end in ".kar" (case
421         * insensitive).
422         * 
423         * @param dir
424         *            The root of the local repository that contains KAR files
425         * @param depth
426         *            The maximum recursion depth
427         */
428        private void findKarsRecursive(File dir, int depth,
429                        DefaultMutableTreeNode tn) {
430                if (isDebugging)
431                        log.debug(depth + ": " + dir.toString());
432                if (!dir.exists()) {
433                        log.warn(dir.toString() + " does not exist");
434                        return;
435                }
436                if (!dir.isDirectory()) {
437                        log.warn(dir.toString() + " is not a directory");
438                        return;
439                }
440                if (depth < 0) {
441                        log.warn(dir.toString() + " is too deep");
442                        return;
443                }
444
445                File[] listing = dir.listFiles();
446                for (int i = 0; i < listing.length; i++) {
447                        File currentListing = listing[i];
448                        if (currentListing.isDirectory()) {
449                                if (currentListing.getName().equals(".svn")) {
450                                        // skip .svn folders
451                                } else if (currentListing.getName().contains(".")) {
452                                        // skip any folders that contain periods
453                                        // ptolemy cannot handle periods in NamedObj Names.
454                                    System.out.println("WARNING: skipping due to periods: " + currentListing);
455                                } else {
456                                        DefaultMutableTreeNode dmtn = new DefaultMutableTreeNode(
457                                                        currentListing);
458                                        tn.add(dmtn);
459                                        findKarsRecursive(currentListing, (depth - 1), dmtn);
460                                }
461                        } else {
462                                if (currentListing.getName().toLowerCase().endsWith(".kar")) {
463                                        _karFiles.addElement(currentListing);
464                                } else if(currentListing.getName().toLowerCase().endsWith(".xml")) {
465                                    _xmlFiles.addElement(currentListing);
466                                }
467                        }
468                }
469        }
470
471        /**
472         * 
473         */
474        private void initLocalSaveRepo() {
475                File localSaveRepoFile = new File(_localSaveRepoFileName);
476
477                if (localSaveRepoFile.exists()) {
478                        if (isDebugging) {
479                                log.debug("localSaveRepo exists: "
480                                                + localSaveRepoFile.toString());
481                        }
482
483                        try {
484                                InputStream is = null;
485                                ObjectInput oi = null;
486                                try {
487                                        is = new FileInputStream(localSaveRepoFile);
488                                    oi = new ObjectInputStream(is);
489                                    Object newObj = oi.readObject();
490                                    setLocalSaveRepo((File) newObj);
491                                    return;
492                                } finally {
493                                    if(oi != null) {
494                                        oi.close();
495                                    }
496                                    if(is != null) {
497                                        is.close();
498                                    }
499                                }
500                        } catch (Exception e1) {
501                                // problem reading file, try to delete it
502                                log.warn("Exception while reading localSaveRepoFile: "
503                                                + e1.getMessage());
504                                try {
505                                        localSaveRepoFile.delete();
506                                } catch (Exception e2) {
507                                        log.warn("Unable to delete localSaveRepoFile: "
508                                                        + e2.getMessage());
509                                }
510                        }
511                }
512                try {
513                        setDefaultSaveRepo();
514                } catch (Exception e) {
515                        e.printStackTrace();
516                }
517        }
518
519        /**
520         * @param directory
521         */
522        public void setLocalSaveRepo(File dir) {
523                if (isDebugging)
524                        log.debug("setLocalSaveRepo(" + dir + ")");
525                if (getRepositoryForFile(dir) != null) {
526                        _localSaveRepo = new LocalRepository(dir);
527                        serializeLocalSaveRepo();
528                }
529        }
530
531        /**
532         * Set the default Save repository.
533         * 
534         * @throws Exception
535         */
536        public void setDefaultSaveRepo() {
537                if (isDebugging)
538                        log.debug("setDefaultSaveRepo()");
539
540                // Use the default workflows directory
541                
542                // see if it's the root of a local repository
543                LocalRepository repo = getRepositoryForFile(_defaultUserWorkflowDirectory);
544                if(repo == null) {
545                    // see if it's a subdirectory in a local repository
546                    repo = getContainingLocalRepository(_defaultUserWorkflowDirectory);
547                }
548                
549                if(repo != null) {
550                        setLocalSaveRepo(repo.getDefaultDirectory());
551                        return;
552                }
553
554                // If there is no default workflows directory
555                // Set the save repo to the first local repo in the list
556                for (LocalRepository localRepo : _localRepositories.keySet()) {
557                        setLocalSaveRepo(localRepo.getDefaultDirectory());
558                        break;
559                }
560        }
561
562        /**
563         * Set the default local repositories to be the kar directories for each of
564         * the modules in the system along with a default workflows directory.
565         */
566        public void setDefaultLocalRepos() {
567                if (isDebugging)
568                        log.debug("setDefaultLocalRepos()");
569                
570
571                // Set up a default list of local repository directories
572                _localRepositories = new LinkedHashMap<LocalRepository, String>();
573
574                try {
575                        String deleteAll = "delete from " + KAR_LOCAL_REPOS_TABLE_NAME;
576                        if (isDebugging)
577                                log.debug(deleteAll);
578                        _stmt.executeUpdate(deleteAll);
579                } catch (SQLException sqle) {
580                        log.error(sqle.getMessage());
581                        sqle.printStackTrace();
582                }
583                
584                final DotKeplerManager dkm = DotKeplerManager.getInstance();
585
586                for (Module module : ModuleTree.instance()) {
587                        if (isDebugging) log.debug("Checking for kar directory in " + module.getStemName());
588                        //String modName = m.getName();
589                        String modName = module.getStemName();
590                        File modDir = dkm.getPersistentModuleDirectory(modName);
591                        File karDir = new File(modDir, "kar");
592
593                        if (karDir.isDirectory() && karDir.exists()) {
594                                if (isDebugging)
595                                        log.debug(karDir + " " + modName);
596                                try {
597                                        String repoName = getLocalRepositoryName(modName);
598                                        addLocalRepoRootDir(karDir, repoName);
599                                } catch (Exception e) {
600                                    MessageHandler.error("Error adding local repository " + karDir, e);
601                                }
602                        }
603                        
604                        // NOTE: use the fully versioned name of the module instead of
605                        // the name without a version since the demo workflows can 
606                        // change between versions of the same module.
607                        // 
608            File workflowDir = dkm.getPersistentModuleWorkflowsDir(module.getName());
609            
610            // only add the demos to the library.
611            // <module>/workflows/data may contain XML files that generate
612            // errors when parsed.
613            // see http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5643
614            File demoDir = new File(workflowDir, "demos");
615                        if(demoDir.isDirectory() && demoDir.exists()) {
616                if (isDebugging)
617                    log.debug(demoDir + " " + modName);
618                try {
619                                        String repoName = getLocalRepositoryName(modName);
620                    addLocalRepoRootDir(demoDir, repoName);
621                } catch (Exception e) {
622                    MessageHandler.error("Error adding local repository " + karDir, e);
623                }                           
624                        }
625                        /*
626                        else if (modName.equals(Module.PTOLEMY) || modName.matches(Module.PTOLEMY+"-\\d+\\.\\d+") 
627                        || modName.matches(Module.PTOLEMY_KEPLER+"-\\d+\\.\\d+")) {
628                            
629                Project project = ProjectLocator.getAntProject();
630                // NOTE: getAntProject() may return null; in this case
631                // create a new one.
632                if (project == null) {
633                    project = new Project();
634                }
635                final FileSet fileSet = new FileSet();
636                fileSet.setProject(project);
637                fileSet.setDir(module.getSrc());
638                XXX space added to prevent closing comment
639                fileSet.setIncludes("** /demo");
640                fileSet.setExcludesfile(new File(project.getBaseDir(),
641                        "build-area/settings/ptolemy-excludes"));
642                final String[] files = fileSet.getDirectoryScanner()
643                        .getIncludedDirectories();
644                for (String name : files) {
645                    String repoName = modName;
646                    repoName = repoName.substring(0, 1).toUpperCase()
647                            + repoName.substring(1);
648                    try {
649                        System.out.println(name);
650                        addLocalRepoRootDir(new File(module.getSrc(), name),
651                                repoName);
652                    } catch (Exception e) {
653                        MessageHandler.error("Error adding local repository " + karDir, e);
654                    }
655                }
656                        }
657                   */
658                }
659
660                // Include a default workflows directory
661                try {
662                        addLocalRepoRootDir(_defaultUserWorkflowDirectory, dkm.getPersistentUserWorkflowsDirName());
663                } catch (Exception e) {
664                        e.printStackTrace();
665                }
666
667                if (getLocalRepositories().size() <= 0) {
668                        log.error("No local repositories specified.");
669                }
670
671        }
672
673        /**
674         * Serialize the local save repository to a file on disk so it can be loaded
675         * the next time Kepler starts.
676         */
677        private void serializeLocalSaveRepo() {
678                if (isDebugging)
679                        log.debug("serializeLocalSaveRepo()");
680
681                File localSaveRepoFile = new File(_localSaveRepoFileName);
682                if (localSaveRepoFile.exists()) {
683                        if (isDebugging)
684                                log.debug("delete " + localSaveRepoFile);
685                        localSaveRepoFile.delete();
686                }
687                try {
688                        OutputStream os = new FileOutputStream(localSaveRepoFile);
689                        ObjectOutputStream oos = new ObjectOutputStream(os);
690                        oos.writeObject(_localSaveRepo.getDefaultDirectory());
691                        oos.flush();
692                        oos.close();
693                        if (isDebugging) {
694                                log.debug("wrote " + localSaveRepoFile);
695                        }
696                } catch (FileNotFoundException e) {
697                        e.printStackTrace();
698                } catch (IOException e) {
699                        e.printStackTrace();
700                }
701        }
702
703        /**
704         * Save the local repo dirs to a private variable so we can determine when
705         * they have changed between build points.
706         */
707        public void setCheckpoint() {
708                _checkpointRepos = (LinkedHashMap<LocalRepository, String>) getLocalRepositories()
709                                .clone();
710        }
711
712        /**
713         * Reset the list of local repositories to be what it was the last time the
714         * setCheckpoint() method was called.
715         */
716        public void restoreCheckpoint() {
717                _localRepositories = (LinkedHashMap<LocalRepository, String>) _checkpointRepos
718                                .clone();
719        }
720
721        /**
722         * Check to see if the LocalRepositories have changed since the last time
723         * setCheckpoint() was called.
724         * 
725         * @return true if the local repositories have changed
726         */
727        public boolean changedSinceCheckpoint() {
728
729                if (!_checkpointRepos.equals(getLocalRepositories())) {
730                        return true;
731                }
732                return false;
733        }
734
735        public boolean isLocalRepositoryName(String name) {
736                for (String l : getLocalRepositories().values()) {
737                        if (l.equals(name)) {
738                                return true;
739                        }
740                }
741
742                return false;
743        }
744
745        /**
746         * This method only removes the given directory from the in-memory repository
747         * list.  To update the database you must call the synchronizeDB() method.
748         * 
749         * @param directory
750         * @throws Exception
751         *             if the directory could not be removed
752         */
753        public void removeLocalRepoRootDir(File directory) throws Exception {
754                if (isDebugging)
755                        log.debug("removeLocalRepoRootDir(" + directory + ")");
756
757                // do not remove anything if there is only one local repo left
758                if (_localRepositories.size() == 1) {
759                        throw new Exception(
760                                        "There must always be at least one local repository directory");
761                }
762                LocalRepository repo = getRepositoryForFile(directory);
763                if(repo != null) {
764                        boolean isSaveDir = false;
765                        if (getSaveRepository().equals(directory)) {
766                                isSaveDir = true;
767                        }
768                        
769                        int numLeft = repo.removeDir(directory);
770                        if(numLeft == 0) {
771                            _localRepositories.remove(repo);
772                        }
773                        
774                        if (isSaveDir) {
775                                setDefaultSaveRepo();
776                        }
777                } else {
778                        throw new Exception(
779                                        "Unable to remove directory "
780                                                        + directory
781                                                        + "\n No Local Repository directory matching that name was found.");
782                }
783        }
784        
785        /**
786         * Synchronize the KAR_LOCAL_REPOS table with the _localRepositories private variable list.
787         * This method only removes rows from the table that are not in the list.
788         * It does not add rows to the table for extra entries that are in the list.
789         */
790        public void synchronizeDB() {
791
792                LinkedHashMap<LocalRepository, String> localRepos = selectReposFromDB();
793                for (LocalRepository repo : localRepos.keySet()) {
794                        if (!_localRepositories.containsKey(repo)) {
795                                try {
796                                        // this will cascade deletion of KARs and KAR contents
797                                        // from the tables
798                                        _deletePrepStmt.setString(1, repo.toString());
799                                        _deletePrepStmt.executeUpdate();
800                                        _conn.commit();
801                                } catch (SQLException sqle) {
802                                        sqle.printStackTrace();
803                                }
804                        }
805                }
806        }
807
808        /**
809         * Change the name of a local repository.
810         * 
811         * @param directory the default root directory of the repository
812         * @param name
813         * @throws Exception
814         */
815        public void setLocalRepoName(File directory, String name) throws Exception {
816                if (_localRepositories.containsValue(name)) {
817                        throw new Exception(
818                                        "This name is already assigned to a local repository directory.");
819                }
820                Iterator<NamedOntModel> models = OntologyCatalog.instance()
821                                .getNamedOntModels();
822                while (models.hasNext()) {
823                        if (models.next().getName().equals(name)) {
824                                throw new Exception(
825                                                "This name is already being used for an ontology.");
826                        }
827                }
828                LocalRepository repo = getRepositoryForFile(directory);
829                if(repo == null) {
830                    throw new Exception("No repository found with directory " + directory);
831                }
832                try {
833                        _updateNamePrepStmt.setString(1, name);
834                        _updateNamePrepStmt.setString(2, repo.getDirectoriesAsString());
835                        _updateNamePrepStmt.executeUpdate();
836                        _conn.commit();
837                        String oldName = _localRepositories.put(repo, name);
838                        if (isDebugging)
839                                log.debug(oldName + " was changed to " + name);
840                } catch (SQLException sqle) {
841                        log.warn(sqle.getMessage());
842                }
843        }
844
845        /**
846         * Given a file, return true if it is in a local repository, false if it is
847         * not.
848         * 
849         * @param aFile
850         * @return
851         */
852        public boolean isInLocalRepository(File aFile) {
853            LocalRepository containingRepo = getContainingLocalRepository(aFile);
854                if (containingRepo != null) {
855                        return true;
856                }
857                return false;
858        }
859
860        /**
861         * Given a file, return the local repository that it is in or null if it is
862         * not in a local repository.  This also returns null if the file passed in
863         * is a local repository.
864         * 
865         * @param aFile
866         * @return
867         */
868        public LocalRepository getContainingLocalRepository(File aFile) {
869                for (LocalRepository repo : getLocalRepositories().keySet()) {
870                    for(File repoRootDir : repo.getDirectories()) {
871                        try {
872                                if (FileUtil.isSubdirectory(repoRootDir, aFile)) {
873                                        return repo;
874                                }
875                        } catch (Exception e) {
876                                e.printStackTrace();
877                        }
878                    }
879                }
880                return null;
881        }
882
883        /**
884         * Convenience method for addLocalRepoRootDir(File, String)
885         * 
886         * @param directory
887         * @throws Exception
888         */
889        public void addLocalRepoRootDir(File directory) throws Exception {
890                addLocalRepoRootDir(directory, directory.getName());
891        }
892
893        /** Add a local repository for a given root directory and name. If
894         *  a repository with that name already exists, the directory is added
895         *  to the list of root directories for that repository.
896         * @param directory
897         * @throws Exception
898         */
899        public void addLocalRepoRootDir(File directory, String name)
900                        throws Exception {
901                if (isDebugging)
902                        log.debug("addLocalRepoRootDir(" + directory + ", " + name + ")");
903                if (!directory.isDirectory()) {
904                        throw new Exception(
905                                        "The specified local repository root must be a directory");
906                }
907                String selFileName = FileUtil.clean(directory);
908                
909                LocalRepository existingRepo = null;
910                
911                // check to make sure this directory is not a sub directory
912                // of any existing local repositories
913                for (Map.Entry<LocalRepository, String> entry : getLocalRepositories().entrySet()) {
914                        
915                    final LocalRepository repo = entry.getKey();
916                    
917                    for(File localDir : repo.getDirectories()) {
918                        // make sure this selection doesn't match an existing local repository exactly
919                        if (FileUtil.clean(localDir).equals(selFileName)) {
920                                throw new Exception(
921                                                "Local repository root directory was not added because \n"
922                                                                + directory
923                                                                + "\n is already listed as a local repository root directory.");        
924                        }
925                        
926                        // make sure this selection is not a subdirecctory of an existing local repository
927                        boolean selectionIsSub = FileUtil.isSubdirectory(localDir, directory);
928                        if (selectionIsSub) {
929                                throw new Exception(
930                                                "Local repository was not added because \n"
931                                                                + directory + "\n is a subdirectory of \n"
932                                                                + localDir);
933                        }
934                        
935                        // make sure this selection does not contain an existing local repository as a subdirectory
936                        boolean repoIsSub = FileUtil.isSubdirectory(directory, localDir);
937                        if (repoIsSub) {
938                                throw new Exception(
939                                                "Local repository was not added because \n"
940                                                                + localDir + "\n is a subdirectory of \n"
941                                                                + directory);
942                        }
943                    }
944                    
945                    final String repoName = entry.getValue();
946                    if(repoName.equals(name)) {
947                        existingRepo = repo;
948                    }
949                }
950
951        try {
952
953                // see if a repository with that name already exists
954                if(existingRepo == null) {
955                        _insertPrepStmt.setString(1, name);
956                        _insertPrepStmt.setString(2, directory.toString());
957                        _insertPrepStmt.executeUpdate();
958                        _localRepositories.put(new LocalRepository(directory), name);
959                } else {
960                    String paths = existingRepo.addDirectory(directory);
961                    _updatePathPrepStmt.setString(1, paths);
962                    _updatePathPrepStmt.setString(2, name);
963                _updatePathPrepStmt.executeUpdate();
964                }
965
966                _conn.commit();
967                
968        } catch (SQLException sqle) {
969            sqle.printStackTrace();
970        }
971        }
972
973        public LinkedHashMap<LocalRepository, String> getLocalRepositories() {
974                return _localRepositories;
975        }
976
977        public File getSaveRepository() {
978                if(_localSaveRepo != null) {
979                    List<File> dirs = _localSaveRepo.getDirectories();
980                    if(!dirs.isEmpty()) {
981                        return dirs.get(0);
982                    }
983                }
984            return null;
985        }
986
987        /** Get the repository for a file. Returns null if no repository
988     *  has a root directory matching this path.
989     */
990    public LocalRepository getRepositoryForFile(File file) {
991        for(LocalRepository repo : getLocalRepositories().keySet()) {
992            if(repo.isFileRepoDirectory(file)) {
993                return repo;
994            }
995        }
996        return null;
997    }
998
999        /**
1000         * Method for getting an instance of this singleton class.
1001         */
1002        public static LocalRepositoryManager getInstance() {
1003                return LocalRepositoryManagerHolder.INSTANCE;
1004        }
1005
1006        private static class LocalRepositoryManagerHolder {
1007                private static final LocalRepositoryManager INSTANCE = new LocalRepositoryManager();
1008        }
1009        
1010        /** Get the display name of a local repository for a module. */
1011        public static String getLocalRepositoryName(String moduleName) throws Exception {
1012                
1013                String name = null;
1014                
1015                // see if the module name is customized
1016                Module module = ModuleTree.instance().getModuleByStemName(moduleName);
1017                if(module != null) {
1018                        ConfigurationProperty property = ConfigurationManager.getInstance().getProperty(module);
1019                        if(property != null) {
1020                                ConfigurationProperty nameProperty = property.getProperty(MODULE_DISPLAY_NAME);
1021                                if(nameProperty != null) {
1022                                        name = nameProperty.getValue();
1023                                }
1024                        }
1025                }
1026                
1027                // the module name was not customized, so return a name the same as the
1028                // module name with the first letter upper-case.
1029                if(name == null) {
1030                        name = moduleName;
1031                        name = name.substring(0, 1).toUpperCase() + name.substring(1);
1032                }
1033                
1034                return name;
1035        }
1036        
1037        /** A repository on the local disk. The repository may have more than one
1038         *  root directory.
1039         */
1040        public static class LocalRepository {
1041
1042            /** Get the default directory. */
1043        public File getDefaultDirectory() {
1044            return _directories.get(0);
1045        }
1046
1047        /** Returns true if the given file is one of the directories of this repository. */ 
1048        public boolean isFileRepoDirectory(File file) {
1049            for(File dir : _directories) {
1050                if(dir.equals(file)) {
1051                    return true;
1052                }
1053            }
1054            return false;
1055        }
1056
1057        /** Get the absolute path of the default root directory. */
1058        @Override
1059        public String toString() {
1060            //System.out.println("in toString from: " + new Exception().getStackTrace()[1]);
1061            return getDefaultDirectory().toString();
1062        }
1063        
1064        /** Returns true iff the given object is a LocalRepository containing
1065         *  the same directories this LocalRepository.
1066         */
1067        @Override
1068        public boolean equals(Object object) {
1069            if(object == null || !(object instanceof LocalRepository)) {
1070                return false;
1071            } else {
1072                // see if the directories are the same
1073                return _directoriesString.equals(((LocalRepository)object)._directoriesString);
1074            }
1075        }
1076        
1077        /** Get the directories in separated by File.pathSeparator character. */
1078        private String getDirectoriesAsString() {
1079                return _directoriesString;
1080        }
1081        
1082        /** Get the hash code of this LocalRepository. */
1083        @Override
1084        public int hashCode() {
1085                // use the hash code of the directories in this repository
1086                return _directoriesString.hashCode();
1087        }
1088        
1089        /** Remove a root directory.
1090         *  @return the number of remaining root directories.
1091         */
1092        public int removeDir(File directory) {
1093            _directories.remove(directory);
1094            _updateDirectoriesString();
1095            return _directories.size();
1096        }
1097
1098        /** Create a new Repository with a root directory. */
1099        private LocalRepository(File dir) {
1100                addDirectory(dir);
1101        }
1102
1103        /** Create a new Repository with a set of root directories. 
1104         * @param paths A string of paths separated by File.pathSeparator.
1105         */
1106        private LocalRepository(String paths) {
1107            String[] parts = paths.split(File.pathSeparator);
1108            for(String part : parts) {
1109                addDirectory(new File(part));
1110            }
1111        }
1112
1113        /** Add a root directory. 
1114         * @return A string of paths separated by File.pathSeparator.
1115         */
1116        private String addDirectory(File directory) {
1117            _directories.add(directory);
1118            _updateDirectoriesString();
1119            return _directoriesString;
1120        }
1121
1122        /** Get the root directories. */
1123        private List<File> getDirectories() {
1124            return new LinkedList<File>(_directories);
1125        }
1126        
1127        /** Update _directoriesString to be the sorted list of directories
1128         *  in _directories separated by File.pathSeparator. */
1129        private void _updateDirectoriesString() {
1130                // get the directories as an array and sort lexicographically
1131                final File[] array = _directories.toArray(new File[_directories.size()]);
1132                Arrays.sort(array);
1133                
1134                StringBuilder buf = new StringBuilder();
1135            for(int i = 0; i < array.length - 1; i++) {
1136                buf.append(array[i].getAbsolutePath());
1137                buf.append(File.pathSeparatorChar);
1138            }
1139            buf.append(array[array.length - 1]);
1140            _directoriesString = buf.toString();
1141        }
1142        
1143        /** The root directories. */
1144        private List<File> _directories = new LinkedList<File>();
1145        
1146        /** The root directories separated by File.pathSeparator character. */
1147        private String _directoriesString = "";
1148        }
1149        
1150}