001package co.codewizards.cloudstore.local.persistence; 002 003import static co.codewizards.cloudstore.core.util.Util.*; 004 005import java.io.File; 006import java.util.Collections; 007import java.util.Date; 008import java.util.LinkedList; 009import java.util.List; 010import java.util.UUID; 011 012import javax.jdo.JDOHelper; 013import javax.jdo.annotations.Discriminator; 014import javax.jdo.annotations.DiscriminatorStrategy; 015import javax.jdo.annotations.Index; 016import javax.jdo.annotations.Indices; 017import javax.jdo.annotations.NullValue; 018import javax.jdo.annotations.PersistenceCapable; 019import javax.jdo.annotations.Persistent; 020import javax.jdo.annotations.Queries; 021import javax.jdo.annotations.Query; 022import javax.jdo.annotations.Unique; 023 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027@PersistenceCapable 028@Discriminator(strategy=DiscriminatorStrategy.VALUE_MAP) 029@Unique(name="RepoFile_parent_name", members={"parent", "name"}) 030@Indices({ 031 @Index(name="RepoFile_parent", members={"parent"}), 032 @Index(name="RepoFile_localRevision", members={"localRevision"}), 033}) 034@Queries({ 035 @Query(name="getChildRepoFile_parent_name", value="SELECT UNIQUE WHERE this.parent == :parent && this.name == :name"), 036 @Query(name="getChildRepoFiles_parent", value="SELECT WHERE this.parent == :parent"), 037 @Query( 038 name="getRepoFilesChangedAfter_localRevision_exclLastSyncFromRepositoryId", 039 value="SELECT WHERE this.localRevision > :localRevision && (this.lastSyncFromRepositoryId == null || this.lastSyncFromRepositoryId != :lastSyncFromRepositoryId)"), // TODO this necessary == null is IMHO a DN bug! 040}) 041public abstract class RepoFile extends Entity implements AutoTrackLocalRevision { 042 private static final Logger logger = LoggerFactory.getLogger(RepoFile.class); 043 044 private RepoFile parent; 045 046 @Persistent(nullValue=NullValue.EXCEPTION) 047 private String name; 048 049 private long localRevision; 050 051 @Persistent(nullValue = NullValue.EXCEPTION) 052 private Date lastModified; 053 054 private String lastSyncFromRepositoryId; 055 056 public RepoFile getParent() { 057 return parent; 058 } 059 public void setParent(RepoFile parent) { 060 this.parent = parent; 061 } 062 063 public String getName() { 064 return name; 065 } 066 public void setName(String name) { 067 this.name = name; 068 } 069 070 /** 071 * {@inheritDoc} 072 * <p> 073 * Note that this does not include modifications of children (in case this is a directory). 074 * If a child is modified, solely this child's localRevision is updated. 075 */ 076 @Override 077 public long getLocalRevision() { 078 return localRevision; 079 } 080 @Override 081 public void setLocalRevision(long localRevision) { 082 if (this.localRevision != localRevision) { 083 if (logger.isDebugEnabled()) { 084 LocalRepository localRepository = new LocalRepositoryDAO().persistenceManager(JDOHelper.getPersistenceManager(this)).getLocalRepositoryOrFail(); 085 logger.debug("setLocalRevision: localRepositoryId={} path='{}' old={} new={}", localRepository.getRepositoryId(), getPath(), this.localRevision, localRevision); 086 } 087 this.localRevision = localRevision; 088 } 089 } 090 091 /** 092 * Gets the path within the repository from the {@link LocalRepository#getRoot() root} (including) to <code>this</code> (including). 093 * <p> 094 * The first element in the list is the {@code root}. The last element is <code>this</code>. 095 * <p> 096 * If this method is called on the {@code root} itself, the result will be a list with one single element (the root itself). 097 * @return the path within the repository from the {@link LocalRepository#getRoot() root} (including) to <code>this</code> (including). Never <code>null</code>. 098 */ 099 public List<RepoFile> getPathList() { 100 LinkedList<RepoFile> path = new LinkedList<RepoFile>(); 101 RepoFile rf = this; 102 while (rf != null) { 103 path.addFirst(rf); 104 rf = rf.getParent(); 105 } 106 return Collections.unmodifiableList(path); 107 } 108 109 /** 110 * Gets the path from the root to <code>this</code>. 111 * <p> 112 * The path's elements are separated by a slash ("/"). The path starts with a slash (like an absolute path), but 113 * is relative to the repository's local root. 114 * @return the path from the root to <code>this</code>. Never <code>null</code>. The repository's root itself has the path "/". 115 */ 116 public String getPath() { 117 StringBuilder sb = new StringBuilder(); 118 for (RepoFile repoFile : getPathList()) { 119 if (sb.length() == 0 || sb.charAt(sb.length() - 1) != '/') 120 sb.append('/'); 121 122 sb.append(repoFile.getName()); 123 } 124 return sb.toString(); 125 } 126 127 /** 128 * Gets the {@link File} represented by this {@link RepoFile} inside the given repository's {@code localRoot} directory. 129 * @param localRoot the repository's root directory. 130 * @return the {@link File} represented by this {@link RepoFile} inside the given repository's {@code localRoot} directory. 131 */ 132 public File getFile(File localRoot) { 133 assertNotNull("localRoot", localRoot); 134 File result = localRoot; 135 for (RepoFile repoFile : getPathList()) { 136 if (repoFile.getParent() == null) // skip the root 137 continue; 138 139 result = new File(result, repoFile.getName()); 140 } 141 return result; 142 } 143 144 /** 145 * Gets the timestamp of the file's last modification. 146 * <p> 147 * It reflects the {@link File#lastModified() File.lastModified} property. 148 * @return the timestamp of the file's last modification. 149 */ 150 public Date getLastModified() { 151 return lastModified; 152 } 153 public void setLastModified(Date lastModified) { 154 this.lastModified = lastModified; 155 } 156 157 public UUID getLastSyncFromRepositoryId() { 158 return lastSyncFromRepositoryId == null ? null : UUID.fromString(lastSyncFromRepositoryId); 159 } 160 public void setLastSyncFromRepositoryId(UUID repositoryId) { 161 this.lastSyncFromRepositoryId = repositoryId == null ? null : repositoryId.toString(); 162 } 163}