package co.codewizards.cloudstore.local.persistence;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
import javax.jdo.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import co.codewizards.cloudstore.core.oio.File;
import co.codewizards.cloudstore.core.util.AssertUtil;
public class RepoFileDao extends Dao<RepoFile, RepoFileDao> {
private static final Logger logger = LoggerFactory.getLogger(RepoFileDao.class);
private Directory localRootDirectory;
private DirectoryCache directoryCache;
private static class DirectoryCache {
private static final int MAX_SIZE = 50;
private final Map<File, Directory> file2DirectoryCache = new HashMap<File, Directory>();
private final Map<Directory, File> directory2FileCache = new HashMap<Directory, File>();
private final LinkedList<Directory> directoryCacheList = new LinkedList<Directory>();
public Directory get(final File file) {
return file2DirectoryCache.get(file);
}
public void put(final File file, final Directory directory) {
file2DirectoryCache.put(assertNotNull(file, "file"), assertNotNull(directory, "directory"));
directory2FileCache.put(directory, file);
directoryCacheList.remove(directory);
directoryCacheList.addLast(directory);
removeOldEntriesIfNecessary();
}
public void remove(final Directory directory) {
final File file = directory2FileCache.remove(directory);
file2DirectoryCache.remove(file);
}
public void remove(final File file) {
final Directory directory = file2DirectoryCache.remove(file);
directory2FileCache.remove(directory);
}
private void removeOldEntriesIfNecessary() {
while (directoryCacheList.size() > MAX_SIZE) {
final Directory directory = directoryCacheList.removeFirst();
remove(directory);
}
}
}
/**
* Get the child of the given {@code parent} with the specified {@code name}.
* @param parent the {@link RepoFile#getParent() parent} of the queried child.
* @param name the {@link RepoFile#getName() name} of the queried child.
* @return the child matching the given criteria; <code>null</code>, if there is no such object in the database.
*/
public RepoFile getChildRepoFile(final RepoFile parent, final String name) {
final Query query = pm().newNamedQuery(getEntityClass(), "getChildRepoFile_parent_name");
final RepoFile repoFile = (RepoFile) query.execute(parent, name);
return repoFile;
}
/**
* Get the {@link RepoFile} for the given {@code file} in the file system.
* @param localRoot the repository's root directory in the file system. Must not be <code>null</code>.
* @param file the file in the file system for which to query the associated {@link RepoFile}. Must not be <code>null</code>.
* @return the {@link RepoFile} for the given {@code file} in the file system; <code>null</code>, if no such
* object exists in the database.
* @throws IllegalArgumentException if one of the parameters is <code>null</code> or if the given {@code file}
* is not located inside the repository - i.e. it is not a direct or indirect child of the given {@code localRoot}.
*/
public RepoFile getRepoFile(final File localRoot, final File file) throws IllegalArgumentException {
return _getRepoFile(AssertUtil.assertNotNull(localRoot, "localRoot"), AssertUtil.assertNotNull(file, "file"), file);
}
private RepoFile _getRepoFile(final File localRoot, final File file, final File originallySearchedFile) {
if (localRoot.equals(file)) {
return getLocalRootDirectory();
}
final DirectoryCache directoryCache = getDirectoryCache();
final Directory directory = directoryCache.get(file);
if (directory != null)
return directory;
final File parentFile = file.getParentFile();
if (parentFile == null)
throw new IllegalArgumentException(String.format("Repository '%s' does not contain file '%s'!", localRoot, originallySearchedFile));
final RepoFile parentRepoFile = _getRepoFile(localRoot, parentFile, originallySearchedFile);
final RepoFile result = getChildRepoFile(parentRepoFile, file.getName());
if (result instanceof Directory)
directoryCache.put(file, (Directory)result);
return result;
}
public Directory getLocalRootDirectory() {
if (localRootDirectory == null)
localRootDirectory = new LocalRepositoryDao().persistenceManager(pm()).getLocalRepositoryOrFail().getRoot();
return localRootDirectory;
}
/**
* Get the children of the given {@code parent}.
* <p>
* The children are those {@link RepoFile}s whose {@link RepoFile#getParent() parent} equals the given
* {@code parent} parameter.
* @param parent the parent whose children are to be queried. This may be <code>null</code>, but since
* there is only one single instance with {@code RepoFile.parent} being null - the root directory - this
* is usually never <code>null</code>.
* @return the children of the given {@code parent}. Never <code>null</code>, but maybe empty.
*/
public Collection<RepoFile> getChildRepoFiles(final RepoFile parent) {
final Query query = pm().newNamedQuery(getEntityClass(), "getChildRepoFiles_parent");
try {
@SuppressWarnings("unchecked")
final
Collection<RepoFile> repoFiles = (Collection<RepoFile>) query.execute(parent);
return load(repoFiles);
} finally {
query.closeAll();
}
}
/**
* Get those {@link RepoFile}s whose {@link RepoFile#getLocalRevision() localRevision} is greater
* than the given {@code localRevision}.
* @param localRevision the {@link RepoFile#getLocalRevision() localRevision}, after which the files
* to be queried where modified.
* @param exclLastSyncFromRepositoryId the {@link RepoFile#getLastSyncFromRepositoryId() lastSyncFromRepositoryId}
* to exclude from the result set. This is used to prevent changes originating from a repository to be synced back
* to its origin (unnecessary and maybe causing a collision there).
* See <a href="https://github.com/cloudstore/cloudstore/issues/25">issue 25</a>.
* @return those {@link RepoFile}s which were modified after the given {@code localRevision}. Never
* <code>null</code>, but maybe empty.
*/
public Collection<RepoFile> getRepoFilesChangedAfterExclLastSyncFromRepositoryId(final long localRevision, final UUID exclLastSyncFromRepositoryId) {
assertNotNull(exclLastSyncFromRepositoryId, "exclLastSyncFromRepositoryId");
final Query query = pm().newNamedQuery(getEntityClass(), "getRepoFilesChangedAfter_localRevision_exclLastSyncFromRepositoryId");
try {
long startTimestamp = System.currentTimeMillis();
@SuppressWarnings("unchecked")
Collection<RepoFile> repoFiles = (Collection<RepoFile>) query.execute(localRevision, exclLastSyncFromRepositoryId.toString());
logger.debug("getRepoFilesChangedAfter: query.execute(...) took {} ms.", System.currentTimeMillis() - startTimestamp);
startTimestamp = System.currentTimeMillis();
repoFiles = load(repoFiles);
logger.debug("getRepoFilesChangedAfter: Loading result-set with {} elements took {} ms.", repoFiles.size(), System.currentTimeMillis() - startTimestamp);
return repoFiles;
} finally {
query.closeAll();
}
}
@Override
public void deletePersistent(final RepoFile entity) {
getPersistenceManager().flush();
if (entity instanceof Directory)
getDirectoryCache().remove((Directory) entity);
super.deletePersistent(entity);
getPersistenceManager().flush(); // We run *sometimes* into foreign key violations if we don't delete immediately :-(
}
private DirectoryCache getDirectoryCache() {
if (directoryCache == null)
directoryCache = new DirectoryCache();
return directoryCache;
}
}