001package co.codewizards.cloudstore.local.persistence; 002 003import static co.codewizards.cloudstore.core.util.AssertUtil.*; 004 005import java.util.Collection; 006import java.util.HashMap; 007import java.util.LinkedList; 008import java.util.Map; 009import java.util.UUID; 010 011import javax.jdo.PersistenceManager; 012import javax.jdo.Query; 013 014import org.slf4j.Logger; 015import org.slf4j.LoggerFactory; 016 017import co.codewizards.cloudstore.core.oio.File; 018import co.codewizards.cloudstore.core.util.AssertUtil; 019 020public class RepoFileDao extends Dao<RepoFile, RepoFileDao> { 021 private static final Logger logger = LoggerFactory.getLogger(RepoFileDao.class); 022 023 private Directory localRootDirectory; 024 025 private DirectoryCache directoryCache; 026 027 private static class DirectoryCache { 028 private static final int MAX_SIZE = 50; 029 private final Map<File, Directory> file2DirectoryCache = new HashMap<File, Directory>(); 030 private final Map<Directory, File> directory2FileCache = new HashMap<Directory, File>(); 031 private final LinkedList<Directory> directoryCacheList = new LinkedList<Directory>(); 032 033 public Directory get(final File file) { 034 return file2DirectoryCache.get(file); 035 } 036 037 public void put(final File file, final Directory directory) { 038 file2DirectoryCache.put(assertNotNull(file, "file"), assertNotNull(directory, "directory")); 039 directory2FileCache.put(directory, file); 040 directoryCacheList.remove(directory); 041 directoryCacheList.addLast(directory); 042 removeOldEntriesIfNecessary(); 043 } 044 045 public void remove(final Directory directory) { 046 final File file = directory2FileCache.remove(directory); 047 file2DirectoryCache.remove(file); 048 } 049 050 public void remove(final File file) { 051 final Directory directory = file2DirectoryCache.remove(file); 052 directory2FileCache.remove(directory); 053 } 054 055 private void removeOldEntriesIfNecessary() { 056 while (directoryCacheList.size() > MAX_SIZE) { 057 final Directory directory = directoryCacheList.removeFirst(); 058 remove(directory); 059 } 060 } 061 } 062 063 /** 064 * Get the child of the given {@code parent} with the specified {@code name}. 065 * @param parent the {@link RepoFile#getParent() parent} of the queried child. 066 * @param name the {@link RepoFile#getName() name} of the queried child. 067 * @return the child matching the given criteria; <code>null</code>, if there is no such object in the database. 068 */ 069 public RepoFile getChildRepoFile(final RepoFile parent, final String name) { 070 final Query query = pm().newNamedQuery(getEntityClass(), "getChildRepoFile_parent_name"); 071 final RepoFile repoFile = (RepoFile) query.execute(parent, name); 072 return repoFile; 073 } 074 075 /** 076 * Get the {@link RepoFile} for the given {@code file} in the file system. 077 * @param localRoot the repository's root directory in the file system. Must not be <code>null</code>. 078 * @param file the file in the file system for which to query the associated {@link RepoFile}. Must not be <code>null</code>. 079 * @return the {@link RepoFile} for the given {@code file} in the file system; <code>null</code>, if no such 080 * object exists in the database. 081 * @throws IllegalArgumentException if one of the parameters is <code>null</code> or if the given {@code file} 082 * is not located inside the repository - i.e. it is not a direct or indirect child of the given {@code localRoot}. 083 */ 084 public RepoFile getRepoFile(final File localRoot, final File file) throws IllegalArgumentException { 085 return _getRepoFile(AssertUtil.assertNotNull(localRoot, "localRoot"), AssertUtil.assertNotNull(file, "file"), file); 086 } 087 088 private RepoFile _getRepoFile(final File localRoot, final File file, final File originallySearchedFile) { 089 if (localRoot.equals(file)) { 090 return getLocalRootDirectory(); 091 } 092 093 final DirectoryCache directoryCache = getDirectoryCache(); 094 final Directory directory = directoryCache.get(file); 095 if (directory != null) 096 return directory; 097 098 final File parentFile = file.getParentFile(); 099 if (parentFile == null) 100 throw new IllegalArgumentException(String.format("Repository '%s' does not contain file '%s'!", localRoot, originallySearchedFile)); 101 102 final RepoFile parentRepoFile = _getRepoFile(localRoot, parentFile, originallySearchedFile); 103 final RepoFile result = getChildRepoFile(parentRepoFile, file.getName()); 104 if (result instanceof Directory) 105 directoryCache.put(file, (Directory)result); 106 107 return result; 108 } 109 110 public Directory getLocalRootDirectory() { 111 if (localRootDirectory == null) 112 localRootDirectory = new LocalRepositoryDao().persistenceManager(pm()).getLocalRepositoryOrFail().getRoot(); 113 114 return localRootDirectory; 115 } 116 117 /** 118 * Get the children of the given {@code parent}. 119 * <p> 120 * The children are those {@link RepoFile}s whose {@link RepoFile#getParent() parent} equals the given 121 * {@code parent} parameter. 122 * @param parent the parent whose children are to be queried. This may be <code>null</code>, but since 123 * there is only one single instance with {@code RepoFile.parent} being null - the root directory - this 124 * is usually never <code>null</code>. 125 * @return the children of the given {@code parent}. Never <code>null</code>, but maybe empty. 126 */ 127 public Collection<RepoFile> getChildRepoFiles(final RepoFile parent) { 128 final Query query = pm().newNamedQuery(getEntityClass(), "getChildRepoFiles_parent"); 129 try { 130 @SuppressWarnings("unchecked") 131 final Collection<RepoFile> repoFiles = (Collection<RepoFile>) query.execute(parent); 132 return load(repoFiles); 133 } finally { 134 query.closeAll(); 135 } 136 } 137 138 /** 139 * Get those {@link RepoFile}s whose {@link RepoFile#getLocalRevision() localRevision} is greater 140 * than the given {@code localRevision}. 141 * @param localRevision the {@link RepoFile#getLocalRevision() localRevision}, after which the files 142 * to be queried where modified. 143 * @param exclLastSyncFromRepositoryId the {@link RepoFile#getLastSyncFromRepositoryId() lastSyncFromRepositoryId} 144 * to exclude from the result set. This is used to prevent changes originating from a repository to be synced back 145 * to its origin (unnecessary and maybe causing a collision there). 146 * See <a href="https://github.com/cloudstore/cloudstore/issues/25">issue 25</a>. 147 * @return those {@link RepoFile}s which were modified after the given {@code localRevision}. Never 148 * <code>null</code>, but maybe empty. 149 */ 150 public Collection<RepoFile> getRepoFilesChangedAfterExclLastSyncFromRepositoryId(final long localRevision, final UUID exclLastSyncFromRepositoryId) { 151 assertNotNull(exclLastSyncFromRepositoryId, "exclLastSyncFromRepositoryId"); 152 final PersistenceManager pm = pm(); 153 final FetchPlanBackup fetchPlanBackup = FetchPlanBackup.createFrom(pm); 154 final Query query = pm.newNamedQuery(getEntityClass(), "getRepoFilesChangedAfter_localRevision_exclLastSyncFromRepositoryId"); 155 try { 156 clearFetchGroups(); 157 long startTimestamp = System.currentTimeMillis(); 158 @SuppressWarnings("unchecked") 159 Collection<RepoFile> repoFiles = (Collection<RepoFile>) query.execute(localRevision, exclLastSyncFromRepositoryId.toString()); 160 logger.debug("getRepoFilesChangedAfter: query.execute(...) took {} ms.", System.currentTimeMillis() - startTimestamp); 161 162 fetchPlanBackup.restore(pm); 163 startTimestamp = System.currentTimeMillis(); 164 repoFiles = load(repoFiles); 165 logger.debug("getRepoFilesChangedAfter: Loading result-set with {} elements took {} ms.", repoFiles.size(), System.currentTimeMillis() - startTimestamp); 166 167 return repoFiles; 168 } finally { 169 query.closeAll(); 170 fetchPlanBackup.restore(pm); 171 } 172 } 173 174 @Override 175 public void deletePersistent(final RepoFile entity) { 176 getPersistenceManager().flush(); 177 if (entity instanceof Directory) 178 getDirectoryCache().remove((Directory) entity); 179 180 super.deletePersistent(entity); 181 getPersistenceManager().flush(); // We run *sometimes* into foreign key violations if we don't delete immediately :-( 182 } 183 184 private DirectoryCache getDirectoryCache() { 185 if (directoryCache == null) 186 directoryCache = new DirectoryCache(); 187 188 return directoryCache; 189 } 190}