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}