package org.wyona.yarep.impl.repo.vfs; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import org.wyona.yarep.core.NoSuchNodeException; import org.wyona.yarep.core.NoSuchRevisionException; import org.wyona.yarep.core.Node; import org.wyona.yarep.core.NodeStateException; import org.wyona.yarep.core.Property; import org.wyona.yarep.core.Repository; import org.wyona.yarep.core.RepositoryException; import org.wyona.yarep.core.Revision; import org.wyona.yarep.core.Revision; /** * A revision will only read the data from the filesystem if needed. */ public class VirtualFileSystemRevision extends VirtualFileSystemNode implements Revision { private static Logger log = LogManager.getLogger(VirtualFileSystemRevision.class); public static final String PROPERTY_REVISION_CREATION_DATE = "yarep_revisionCreationDate"; public static final String PROPERTY_REVISION_CREATOR = "yarep_revisionCreator"; public static final String PROPERTY_REVISION_TAG = "yarep_revisionTag"; public static final String PROPERTY_REVISION_COMMENT = "yarep_revisionComment"; public static final String CONTENT_FILE_NAME = "content"; private VirtualFileSystemRepository repo2; // INFO: Associated repository private VirtualFileSystemNode node2; // INFO: Associated node protected String revisionName; protected boolean isInitialized = false; /** * Constructor * @param node Node to which this revision belongs to * @param revisionName Name of this revision * @throws RepositoryException */ public VirtualFileSystemRevision(VirtualFileSystemNode node, String revisionName) throws RepositoryException { super(node.getRepository(), node.getPath(), node.getUUID(), false); this.node2 = node; //this.node = node; this.revisionName = revisionName; initContentAndMetaFile(node); // Defer the time consuming initialization until something is actually read from this revision (for performance reasons) } /** * Constructor when node (to which revision belongs to) might not exist anymore * @param repo Repository containing revision * @param path Absolute repository path of node (which might have been deleted) * @param revisionName Name of this revision * @throws RepositoryException */ public VirtualFileSystemRevision(VirtualFileSystemRepository repo, String path, String revisionName) throws RepositoryException { super(repo, path, path, false); this.repo2 = repo; this.revisionName = revisionName; initContentAndMetaFile(repo, path); // Defer the time consuming initialization until something is actually read from this revision (for performance reasons) //log.debug("Check whether associated node (" + path + ", " + revisionName + ") does really not exist anymore..."); if (repo.existsNode(path)) { node2 = (VirtualFileSystemNode) repo.getNode(path); } else { log.warn("Associated node '" + path + "' does really not exist anymore."); } } /** * @param node Node (e.g. '/en/about.html') which revision is associated with */ private void initContentAndMetaFile(VirtualFileSystemNode node) throws RepositoryException { this.metaDir = VirtualFileSystemNode.getRevisionDir(node.getRepository(), VirtualFileSystemNode.getMetaDir(node.getRepository(), node.getUUID()), this.revisionName); // INFO: For example '/Users/michaelwechner/src/yanel/src/realms/yanel-website/data-repo/yarep-meta/en/about.html.yarep/revisions/11/71/84/25/41/025', whereas the revisionName is '1171842541025' //log.debug("Meta directory '" + this.metaDir + "' of node '" + node.getPath() + "' (Revision: " + this.revisionName + ")."); this.contentFile = node.getRevisionContentFile(this.revisionName); // INFO: For example '/Users/michaelwechner/src/yanel/src/realms/yanel-website/data-repo/yarep-meta/en/about.html.yarep/revisions/11/71/84/25/41/025/content' //log.debug("Revision content file: " + this.contentFile.getAbsolutePath()); this.metaFile = node.getRevisionMetaFile(this.revisionName); // INFO: For example '/Users/michaelwechner/src/yanel/src/realms/yanel-website/data-repo/yarep-meta/en/about.html.yarep/revisions/11/71/84/25/41/025/meta' //log.debug("Revision meta file: " + this.metaFile.getAbsolutePath()); } /** * Init content and meta file of a particular revision * @param repo Repository containing revision * @param path Absolute repository path of node (which might have been deleted) */ private void initContentAndMetaFile(VirtualFileSystemRepository repo, String path) throws RepositoryException { //log.debug("Init revision '" + this.revisionName+ "' of node '" + path + "'..."); this.metaDir = VirtualFileSystemNode.getRevisionDir(repo, VirtualFileSystemNode.getMetaDir(repo, path), this.revisionName); // INFO: For example '/Users/michaelwechner/src/yanel/src/realms/yanel-website/data-repo/yarep-meta/en/about.html.yarep/revisions/11/71/84/25/41/025', whereas the revisionName is '1171842541025' //log.debug("Meta directory '" + this.metaDir + "' of node '" + path + "' (Revision: " + this.revisionName + ")."); this.contentFile = new File(metaDir, VirtualFileSystemRevision.CONTENT_FILE_NAME); // INFO: For example '/Users/michaelwechner/src/yanel/src/realms/yanel-website/data-repo/yarep-meta/en/about.html.yarep/revisions/11/71/84/25/41/025/content' //log.debug("Revision content file: " + this.contentFile.getAbsolutePath()); this.metaFile = new File(metaDir, VirtualFileSystemRevision.META_FILE_NAME); // INFO: For example '/Users/michaelwechner/src/yanel/src/realms/yanel-website/data-repo/yarep-meta/en/about.html.yarep/revisions/11/71/84/25/41/025/meta' //log.debug("Revision meta file: " + this.metaFile.getAbsolutePath()); } /** * */ protected void init() throws RepositoryException { if (repo2 != null) { log.debug("Init revision '" + revisionName + "' of node '" + path + "'..."); initContentAndMetaFile(repo2, path); } else if (node2 != null) { log.debug("Init revision '" + revisionName + "' of node '" + node2.getPath() + "'..."); initContentAndMetaFile(node2); } else { log.error("Neither repository nor node!"); } if (log.isDebugEnabled()) { log.debug("VirtualFileSystemRevision: path=" + path + " uuid=" + uuid + " revisionName=" + revisionName); log.debug("contentDir=" + contentDir); log.debug("contentFile=" + contentFile); log.debug("metaDir=" + metaDir); log.debug("metaFile=" + metaFile); } if (!metaFile.exists()) { throw new RepositoryException("Meta file '" + metaFile + "' does not exist."); } readProperties(); isInitialized = true; } /** * @see org.wyona.yarep.impl.repo.fs.VirtualFileSystemNode#addNode(java.lang.String, int) */ public Node addNode(String name, int type) throws RepositoryException { throw new RepositoryException("cannot call this method on a revision"); } /** * @see org.wyona.yarep.impl.repo.fs.VirtualFileSystemNode#checkin() */ public Revision checkin() throws NodeStateException, RepositoryException { throw new RepositoryException("cannot call this method on a revision"); } /** * @see org.wyona.yarep.impl.repo.fs.VirtualFileSystemNode#checkout(java.lang.String) */ public void checkout(String userID) throws NodeStateException, RepositoryException { throw new RepositoryException("cannot call this method on a revision"); } protected Revision createRevision() throws RepositoryException { throw new RepositoryException("cannot call this method on a revision"); } /** * @see org.wyona.yarep.impl.repo.fs.VirtualFileSystemNode#restore(java.lang.String) */ public void restore(String revisionName) throws NoSuchRevisionException, RepositoryException { throw new RepositoryException("cannot call this method on a revision"); } /** * @see org.wyona.yarep.core.Revision#getCreationDate() */ public Date getCreationDate() throws RepositoryException { Property property = getProperty(PROPERTY_REVISION_CREATION_DATE); if (property == null) { return null; } return property.getDate(); } /** * Sets the creation date of this revision. * @param date * @throws RepositoryException */ public void setCreationDate(Date date) throws RepositoryException { setProperty(PROPERTY_REVISION_CREATION_DATE, date); } /** * @see org.wyona.yarep.core.Revision#getCreator() */ public String getCreator() throws RepositoryException { Property property = getProperty(PROPERTY_REVISION_CREATOR); if (property == null) { return null; } return property.getString(); } /** * Sets the creator of this revision. * @param creator user id * @throws RepositoryException */ public void setCreator(String creator) throws RepositoryException { setProperty(PROPERTY_REVISION_CREATOR, creator); } /** * @see org.wyona.yarep.core.Revision#getComment() */ public String getComment() throws RepositoryException { Property property = getProperty(PROPERTY_REVISION_COMMENT); if (property == null) { return null; } return property.getString(); } /** * Sets the comment about this revision. * @param comment * @throws RepositoryException */ public void setComment(String comment) throws RepositoryException { setProperty(PROPERTY_REVISION_COMMENT, comment); } /** * @see org.wyona.yarep.core.Revision#getTag() */ public String getTag() throws RepositoryException { Property tag = getProperty(PROPERTY_REVISION_TAG); if (tag == null) return null; return tag.getString(); } /** * @see org.wyona.yarep.core.Revision#setTag(java.lang.String) */ public void setTag(String tag) throws RepositoryException { setProperty(PROPERTY_REVISION_TAG, tag); } /** * @see org.wyona.yarep.core.Revision#hasTag() */ public boolean hasTag() throws RepositoryException { return hasProperty(PROPERTY_REVISION_TAG); } /** * @see org.wyona.yarep.core.Revision#getRevisionName() */ public String getRevisionName() throws RepositoryException { return this.revisionName; } /** * */ public String toString() { String s = ""; try { s = s + getName() + ", " + getRevisionName() + ", " + getComment() + ", " + getCreator() + ", " + getCreationDate(); } catch (Exception e) { log.error(e.getMessage(), e); s = s + e.getMessage(); } return s; } /** * @see org.wyona.yarep.core.Node#delete() */ @Override public void delete() throws RepositoryException { if (!isInitialized) { init(); } // INFO: Delete from index first, before deleting revision itself! DateIndexerSearcher dis = null; if (repo2 != null) { dis = ((VirtualFileSystemRepository) repo2).getDateIndexerSearcher(path); } else if (node2 != null) { dis = node2.getDateIndexerSearcher(); } else { log.error("Neither repository nor node!"); return; } try { dis.deleteRevision(revisionName); } catch(Exception e) { log.error(e, e); } super.delete(); deleteEmptyDirectories(metaDir); if (node2 != null) { if (node2.hasProperty(VirtualFileSystemNode.PROPERTY_TOTAL_NUMBER_OF_REVISIONS)) { long currentTotal = node2.getProperty(VirtualFileSystemNode.PROPERTY_TOTAL_NUMBER_OF_REVISIONS).getLong(); node2.setProperty(VirtualFileSystemNode.PROPERTY_TOTAL_NUMBER_OF_REVISIONS, currentTotal - 1); } } else { log.warn("Associated node (of this revision '" + getRevisionName() + "') does not seem to exist anymore, hence we cannot update total number of revisions!"); } } /** * Delete empty directories recursively upwards * @param dir Directory which will be deleted if it is empty, e.g. '/Users/michaelwechner/src/yanel/src/realms/yanel-website/data-repo/yarep-meta/en/about.html.yarep/revisions/11/71/84/25/41/025' */ private void deleteEmptyDirectories(File dir) { if (dir.getName().equals(VirtualFileSystemNode.REVISIONS_BASE_DIR)) { return; } if (dir.isDirectory()) { if (isEmpty(dir)) { File parentDir = dir.getParentFile(); dir.delete(); deleteEmptyDirectories(parentDir); } } else { log.warn("No such directory: " + dir.getAbsolutePath()); File parentDir = dir.getParentFile(); if (parentDir != null) { deleteEmptyDirectories(parentDir); } else { return; } } } /** * Check whether a directory is empty * @param dir Directory to be checked * @return true if directory is empty and false otherwise */ private boolean isEmpty(File dir) { String[] filesAndDirs = dir.list(); if (filesAndDirs != null && filesAndDirs.length > 0) { log.warn("DEBUG: Directory '" + dir.getAbsolutePath() + "' is NOT empty."); return false; } log.debug("Directory '" + dir.getAbsolutePath() + "' is empty."); return true; } public InputStream getInputStream() throws RepositoryException { if (!isInitialized) { init(); } return super.getInputStream(); } public long getLastModified() throws RepositoryException { if (!isInitialized) { init(); } return super.getLastModified(); } public Node getNode(String name) throws NoSuchNodeException, RepositoryException { if (!isInitialized) { init(); } return super.getNode(name); } public Node[] getNodes() throws RepositoryException { if (!isInitialized) { init(); } return super.getNodes(); } public OutputStream getOutputStream() throws RepositoryException { if (!isInitialized) { init(); } return super.getOutputStream(); } public Property[] getProperties() throws RepositoryException { if (!isInitialized) { init(); } return super.getProperties(); } /** * @see org.wyona.yarep.core.Node#getProperty(String) */ public Property getProperty(String name) throws RepositoryException { if (!isInitialized) { init(); } return super.getProperty(name); } public long getSize() throws RepositoryException { if (!isInitialized) { init(); } return super.getSize(); } public boolean hasProperty(String name) throws RepositoryException { if (!isInitialized) { init(); } return super.hasProperty(name); } public void removeProperty(String name) throws RepositoryException { if (!isInitialized) { init(); } super.removeProperty(name); } public Property setProperty(String name, boolean value) throws RepositoryException { if (!isInitialized) { init(); } return super.setProperty(name, value); } /** * @see org.wyona.yarep.core.Node#setProperty(String, Date) */ public Property setProperty(String name, Date value) throws RepositoryException { if (!isInitialized) { init(); } return super.setProperty(name, value); } public Property setProperty(String name, double value) throws RepositoryException { if (!isInitialized) { init(); } return super.setProperty(name, value); } public Property setProperty(String name, long value) throws RepositoryException { if (!isInitialized) { init(); } return super.setProperty(name, value); } /** * @see org.wyona.yarep.core.Node#setProperty(String, String) */ public Property setProperty(String name, String value) throws RepositoryException { if (!isInitialized) { init(); } //log.debug("Set property: " + name + ", " + value); return super.setProperty(name, value); } public void setProperty(Property property) throws RepositoryException { if (!isInitialized) { init(); } super.setProperty(property); } }