/* * Copyright (C) 2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.repo.avm; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.SortedMap; /** * This or AVMStore are * the implementors of the operations specified by AVMService. * @author britt */ class AVMRepository { /** * The single instance of AVMRepository. */ private static AVMRepository fgInstance; /** * The current lookup count. */ private ThreadLocal fLookupCount; /** * The node id issuer. */ private Issuer fNodeIssuer; /** * The content id issuer; */ private Issuer fContentIssuer; /** * The layer id issuer. */ private Issuer fLayerIssuer; /** * The file storage directory. */ private String fStorage; /** * Create a new one. It's given issuers and a storage directory. * @param nodeIssuer * @param contentIssuer * @param layerIssuer * @param storage */ public AVMRepository(Issuer nodeIssuer, Issuer contentIssuer, Issuer layerIssuer, String storage) { fStorage = storage; fNodeIssuer = nodeIssuer; fContentIssuer = contentIssuer; fLayerIssuer = layerIssuer; fLookupCount = new ThreadLocal(); fgInstance = this; } /** * Create a file. * @param path The path to the containing directory. * @param name The name for the new file. */ public OutputStream createFile(String path, String name) { fLookupCount.set(1); String[] pathParts = SplitPath(path); AVMStore rep = getAVMStoreByName(pathParts[0], true); // fSession.get().lock(rep, LockMode.UPGRADE); return rep.createFile(pathParts[1], name); } /** * Create a file with the given File as content. * @param path The path to the containing directory. * @param name The name to give the file. * @param data The file contents. */ public void createFile(String path, String name, File data) { fLookupCount.set(1); String[] pathParts = SplitPath(path); AVMStore rep = getAVMStoreByName(pathParts[0], true); rep.createFile(pathParts[1], name, data); } /** * Create a new directory. * @param path The path to the containing directory. * @param name The name to give the directory. */ public void createDirectory(String path, String name) { fLookupCount.set(1); String[] pathParts = SplitPath(path); AVMStore rep = getAVMStoreByName(pathParts[0], true); rep.createDirectory(pathParts[1], name); } /** * Create a new layered directory. * @param srcPath The target indirection for the new layered directory. * @param dstPath The path to the containing directory. * @param name The name for the new directory. */ public void createLayeredDirectory(String srcPath, String dstPath, String name) { if (dstPath.indexOf(srcPath) == 0) { throw new AVMCycleException("Cycle would be created."); } fLookupCount.set(1); String[] pathParts = SplitPath(dstPath); AVMStore rep = getAVMStoreByName(pathParts[0], true); // fSession.get().lock(rep, LockMode.UPGRADE); rep.createLayeredDirectory(srcPath, pathParts[1], name); } /** * Create a new layered file. * @param srcPath The target indirection for the new layered file. * @param dstPath The path to the containing directory. * @param name The name of the new layered file. */ public void createLayeredFile(String srcPath, String dstPath, String name) { fLookupCount.set(1); String[] pathParts = SplitPath(dstPath); AVMStore rep = getAVMStoreByName(pathParts[0], true); rep.createLayeredFile(srcPath, pathParts[1], name); } /** * Create a new AVMStore. * @param name The name to give the new AVMStore. */ public void createAVMStore(String name) { try { getAVMStoreByName(name, false); throw new AVMExistsException("AVMStore exists: " + name); } catch (AVMNotFoundException anf) { // Do nothing. } // Newing up the object causes it to be written to the db. @SuppressWarnings("unused") AVMStore rep = new AVMStoreImpl(this, name); // Special handling for AVMStore creation. NewInAVMStore newInRep = AVMContext.fgInstance.fNewInAVMStoreDAO.getByNode(rep.getRoot()); AVMContext.fgInstance.fNewInAVMStoreDAO.delete(newInRep); } /** * Create a new branch. * @param version The version to branch off. * @param srcPath The path to make a branch from. * @param dstPath The containing directory. * @param name The name of the new branch. */ public void createBranch(int version, String srcPath, String dstPath, String name) { if (dstPath.indexOf(srcPath) == 0) { throw new AVMCycleException("Cycle would be created."); } // Lookup the src node. fLookupCount.set(1); String [] pathParts = SplitPath(srcPath); AVMStore srcRepo = getAVMStoreByName(pathParts[0], false); Lookup sPath = srcRepo.lookup(version, pathParts[1], false); // Lookup the destination directory. fLookupCount.set(1); pathParts = SplitPath(dstPath); AVMStore dstRepo = getAVMStoreByName(pathParts[0], true); Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1], true); DirectoryNode dirNode = (DirectoryNode)dPath.getCurrentNode(); AVMNode srcNode = sPath.getCurrentNode(); AVMNode dstNode = null; // We do different things depending on what kind of thing we're // branching from. I'd be considerably happier if we disallowed // certain scenarios, but Jon won't let me :P (bhp). if (srcNode.getType() == AVMNodeType.PLAIN_DIRECTORY) { dstNode = new PlainDirectoryNodeImpl((PlainDirectoryNode)srcNode, dstRepo); } else if (srcNode.getType() == AVMNodeType.LAYERED_DIRECTORY) { dstNode = new LayeredDirectoryNodeImpl((LayeredDirectoryNode)srcNode, dstRepo); ((LayeredDirectoryNode)dstNode).setLayerID(issueLayerID()); } else if (srcNode.getType() == AVMNodeType.LAYERED_FILE) { dstNode = new LayeredFileNodeImpl((LayeredFileNode)srcNode, dstRepo); } else // This is a plain file. { dstNode = new PlainFileNodeImpl((PlainFileNode)srcNode, dstRepo); } dstNode.setVersionID(dstRepo.getNextVersionID()); dstNode.setAncestor(srcNode); dirNode.putChild(name, dstNode); dirNode.updateModTime(); } /** * Get an output stream to a file. * @param path The full path to the file. * @return An OutputStream. */ public OutputStream getOutputStream(String path) { fLookupCount.set(1); String [] pathParts = SplitPath(path); AVMStore rep = getAVMStoreByName(pathParts[0], true); return rep.getOutputStream(pathParts[1]); } /** * Get a random access file from a file node. * @param version The version id (read-only if not -1) * @param path The path to the file. * @param access The access mode for RandomAccessFile. * @return A RandomAccessFile. */ public RandomAccessFile getRandomAccess(int version, String path, String access) { fLookupCount.set(1); String[] pathParts = SplitPath(path); AVMStore rep = getAVMStoreByName(pathParts[0], true); return rep.getRandomAccess(version, pathParts[1], access); } /** * Rename a node. * @param srcPath Source containing directory. * @param srcName Source name. * @param dstPath Destination containing directory. * @param dstName Destination name. */ public void rename(String srcPath, String srcName, String dstPath, String dstName) { // This is about as ugly as it gets. if (dstPath.indexOf(srcPath + srcName) == 0) { throw new AVMCycleException("Cyclic rename."); } fLookupCount.set(1); String [] pathParts = SplitPath(srcPath); AVMStore srcRepo = getAVMStoreByName(pathParts[0], true); Lookup sPath = srcRepo.lookupDirectory(-1, pathParts[1], true); DirectoryNode srcDir = (DirectoryNode)sPath.getCurrentNode(); AVMNode srcNode = srcDir.lookupChild(sPath, srcName, -1, true); if (srcNode == null) { throw new AVMNotFoundException("Not found: " + srcName); } fLookupCount.set(1); pathParts = SplitPath(dstPath); AVMStore dstRepo = getAVMStoreByName(pathParts[0], true); Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1], true); DirectoryNode dstDir = (DirectoryNode)dPath.getCurrentNode(); AVMNode dstNode = dstDir.lookupChild(dPath, dstName, -1, true); if (dstNode != null) { throw new AVMExistsException("Node exists: " + dstName); } // We've passed the check, so we can go ahead and do the rename. if (srcNode.getType() == AVMNodeType.PLAIN_DIRECTORY) { // If the source is layered then the renamed thing needs to be layered also. if (sPath.isLayered()) { // If this is a rename happening in the same layer we make a new // OverlayedDirectoryNode that is not a primary indirection layer. // Otherwise we do make the new OverlayedDirectoryNode a primary // Indirection layer. This complexity begs the question of whether // we should allow renames from within one layer to within another // layer. Allowing it makes the logic absurdly complex. if (dPath.isLayered() && dPath.getTopLayer().equals(sPath.getTopLayer())) { dstNode = new LayeredDirectoryNodeImpl((PlainDirectoryNode)srcNode, dstRepo, sPath, false); ((LayeredDirectoryNode)dstNode).setLayerID(sPath.getTopLayer().getLayerID()); } else { dstNode = new LayeredDirectoryNodeImpl((DirectoryNode)srcNode, dstRepo, sPath, srcName); ((LayeredDirectoryNode)dstNode).setLayerID(issueLayerID()); } } else { dstNode = new PlainDirectoryNodeImpl((PlainDirectoryNode)srcNode, dstRepo); } } else if (srcNode.getType() == AVMNodeType.LAYERED_DIRECTORY) { // TODO I think I need to subdivide this logic again. // based on whether the destination is a layer or not. if (!sPath.isLayered() || (sPath.isInThisLayer() && srcDir.getType() == AVMNodeType.LAYERED_DIRECTORY && ((LayeredDirectoryNode)srcDir).directlyContains(srcNode))) { // Use the simple 'copy' constructor. dstNode = new LayeredDirectoryNodeImpl((LayeredDirectoryNode)srcNode, dstRepo); ((LayeredDirectoryNode)dstNode).setLayerID(((LayeredDirectoryNode)srcNode).getLayerID()); } else { // If the source node is a primary indirection, then the 'copy' constructor // is used. Otherwise the alternate constructor is called and its // indirection is calculated from it's source context. if (((LayeredDirectoryNode)srcNode).getPrimaryIndirection()) { dstNode = new LayeredDirectoryNodeImpl((LayeredDirectoryNode)srcNode, dstRepo); } else { dstNode = new LayeredDirectoryNodeImpl((DirectoryNode)srcNode, dstRepo, sPath, srcName); } // What needs to be done here is dependent on whether the // rename is to a layered context. If so then it should get the layer id // of its destination parent. Otherwise it should get a new layer // id. if (dPath.isLayered()) { ((LayeredDirectoryNode)dstNode).setLayerID(dPath.getTopLayer().getLayerID()); } else { ((LayeredDirectoryNode)dstNode).setLayerID(issueLayerID()); } } } else if (srcNode.getType() == AVMNodeType.LAYERED_FILE) { dstNode = new LayeredFileNodeImpl((LayeredFileNode)srcNode, dstRepo); } else // This is a plain file node. { dstNode = new PlainFileNodeImpl((PlainFileNode)srcNode, dstRepo); } srcDir.removeChild(srcName); srcDir.updateModTime(); dstNode.setVersionID(dstRepo.getNextVersionID()); dstDir.putChild(dstName, dstNode); dstDir.updateModTime(); dstNode.setAncestor(srcNode); } /** * Uncover a deleted name in a layered directory. * @param dirPath The path to the layered directory. * @param name The name to uncover. */ public void uncover(String dirPath, String name) { fLookupCount.set(1); String [] pathParts = SplitPath(dirPath); AVMStore repo = getAVMStoreByName(pathParts[0], true); repo.uncover(pathParts[1], name); } /** * Snapshot the given repositories. * @param repositories The list of AVMStore name to snapshot. * @return A List of version ids for each newly snapshotted AVMStore. */ public List createSnapshot(List repositories) { List result = new ArrayList(); for (String repName : repositories) { AVMStore repo = getAVMStoreByName(repName, true); result.add(repo.createSnapshot()); } return result; } /** * Create a snapshot of a single AVMStore. * @param store The name of the repository. * @return The version id of the newly snapshotted repository. */ public int createSnapshot(String storeName) { AVMStore store = getAVMStoreByName(storeName, true); return store.createSnapshot(); } /** * Remove a node and everything underneath it. * @param path The path to the containing directory. * @param name The name of the node to remove. */ public void remove(String path, String name) { fLookupCount.set(1); String [] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], true); store.removeNode(pathParts[1], name); } /** * Get rid of all content that lives only in the given AVMStore. * Also removes the AVMStore. * @param name The name of the AVMStore to purge. */ @SuppressWarnings("unchecked") public void purgeAVMStore(String name) { AVMStore store = getAVMStoreByName(name, true); AVMNode root = store.getRoot(); root.setIsRoot(false); VersionRootDAO vrDAO = AVMContext.fgInstance.fVersionRootDAO; List vRoots = vrDAO.getAllInAVMStore(store); for (VersionRoot vr : vRoots) { AVMNode node = vr.getRoot(); node.setIsRoot(false); vrDAO.delete(vr); } List newGuys = AVMContext.fgInstance.fNewInAVMStoreDAO.getByAVMStore(store); for (NewInAVMStore newGuy : newGuys) { AVMContext.fgInstance.fNewInAVMStoreDAO.delete(newGuy); } AVMContext.fgInstance.fAVMStoreDAO.delete(store); } /** * Remove all content specific to a AVMRepository and version. * @param name The name of the AVMStore. * @param version The version to purge. */ public void purgeVersion(String name, int version) { AVMStore store = getAVMStoreByName(name, true); store.purgeVersion(version); } /** * Get an input stream from a file. * @param version The version to look under. * @param path The path to the file. * @return An InputStream. */ public InputStream getInputStream(int version, String path) { fLookupCount.set(1); String [] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], false); return store.getInputStream(version, pathParts[1]); } /** * Get an InputStream from a given version of a file. * @param desc The node descriptor. * @return The InputStream. */ public InputStream getInputStream(AVMNodeDescriptor desc) { fLookupCount.set(1); AVMNode node = AVMContext.fgInstance.fAVMNodeDAO.getByID(desc.getId()); if (node == null) { throw new AVMNotFoundException("Not found."); } if (node.getType() != AVMNodeType.PLAIN_FILE && node.getType() != AVMNodeType.LAYERED_FILE) { throw new AVMWrongTypeException("Not a file."); } FileNode file = (FileNode)node; return file.getContentForRead().getInputStream(); } /** * Get a listing of a directory. * @param version The version to look under. * @param path The path to the directory. * @return A List of FolderEntries. */ public SortedMap getListing(int version, String path) { fLookupCount.set(1); String [] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], false); return store.getListing(version, pathParts[1]); } /** * Get a directory listing from a directory node descriptor. * @param dir The directory node descriptor. * @return A SortedMap listing. */ public SortedMap getListing(AVMNodeDescriptor dir) { fLookupCount.set(1); AVMNode node = AVMContext.fgInstance.fAVMNodeDAO.getByID(dir.getId()); if (node.getType() != AVMNodeType.LAYERED_DIRECTORY && node.getType() != AVMNodeType.PLAIN_DIRECTORY) { throw new AVMWrongTypeException("Not a directory."); } DirectoryNode dirNode = (DirectoryNode)node; return dirNode.getListing(dir); } /** * Get descriptors of all AVMStores. * @return A list of all descriptors. */ @SuppressWarnings("unchecked") public List getAVMStores() { List l = AVMContext.fgInstance.fAVMStoreDAO.getAll(); List result = new ArrayList(); for (AVMStore store : l) { result.add(store.getDescriptor()); } return result; } /** * Get a descriptor for an AVMStore. * @param name The name to get. * @return The descriptor. */ public AVMStoreDescriptor getAVMStore(String name) { AVMStore store = getAVMStoreByName(name, false); return store.getDescriptor(); } /** * Get all version for a given AVMStore. * @param name The name of the AVMStore. * @return A Set will all the version ids. */ public List getAVMStoreVersions(String name) { AVMStore store = getAVMStoreByName(name, false); return store.getVersions(); } /** * Get the set of versions between (inclusive) of the given dates. * From or to may be null but not both. * @param name The name of the AVMRepository. * @param from The earliest date. * @param to The latest date. * @return The Set of version IDs. */ public List getAVMStoreVersions(String name, Date from, Date to) { AVMStore store = getAVMStoreByName(name, false); return store.getVersions(from, to); } /** * Issue a node id. * @return The new id. */ public long issueID() { return fNodeIssuer.issue(); } /** * Issue a content id. * @return The new id. */ public long issueContentID() { return fContentIssuer.issue(); } /** * Issue a new layer id. * @return The new id. */ public long issueLayerID() { return fLayerIssuer.issue(); } /** * Get the indirection path for a layered node. * @param version The version to look under. * @param path The path to the node. * @return The indirection path. */ public String getIndirectionPath(int version, String path) { fLookupCount.set(1); String [] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], false); return store.getIndirectionPath(version, pathParts[1]); } /** * Get the next version id for the given AVMStore. * @param name The name of the AVMStore. * @return The next version id. */ public int getLatestVersionID(String name) { AVMStore store = getAVMStoreByName(name, false); return store.getNextVersionID(); } /** * Get an AVMStore by name. * @param name The name of the AVMStore. * @param write Whether this is called for a write operation. * @return The AVMStore. */ private AVMStore getAVMStoreByName(String name, boolean write) { AVMStore store = AVMContext.fgInstance.fAVMStoreDAO.getByName(name); if (store == null) { throw new AVMNotFoundException("AVMStore not found: " + name); } return store; } /** * Get a descriptor for an AVMStore root. * @param version The version to get. * @param name The name of the AVMStore. * @return The descriptor for the root. */ public AVMNodeDescriptor getAVMStoreRoot(int version, String name) { AVMStore store = getAVMStoreByName(name, false); if (store == null) { throw new AVMNotFoundException("Not found: " + name); } return store.getRoot(version); } // TODO Fix this awful mess regarding cycle detection. /** * Lookup a node. * @param version The version to look under. * @param path The path to lookup. * @return A lookup object. */ public Lookup lookup(int version, String path) { Integer count = fLookupCount.get(); if (count == null) { fLookupCount.set(1); } else { fLookupCount.set(count + 1); } if (fLookupCount.get() > 50) { throw new AVMCycleException("Cycle in lookup."); } String [] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], false); Lookup result = store.lookup(version, pathParts[1], false); if (count == null) { fLookupCount.set(null); } return result; } /** * Lookup a descriptor from a directory descriptor. * @param dir The directory descriptor. * @param name The name of the child to lookup. * @return The child's descriptor. */ public AVMNodeDescriptor lookup(AVMNodeDescriptor dir, String name) { fLookupCount.set(0); AVMNode node = AVMContext.fgInstance.fAVMNodeDAO.getByID(dir.getId()); if (node == null) { throw new AVMNotFoundException("Not found: " + dir.getId()); } if (node.getType() != AVMNodeType.LAYERED_DIRECTORY && node.getType() != AVMNodeType.PLAIN_DIRECTORY) { throw new AVMWrongTypeException("Not a directory."); } DirectoryNode dirNode = (DirectoryNode)node; return dirNode.lookupChild(dir, name); } /** * Get information about layering of a path. * @param version The version to look under. * @param path The full avm path. * @return A LayeringDescriptor. */ public LayeringDescriptor getLayeringInfo(int version, String path) { fLookupCount.set(1); String [] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], false); Lookup lookup = store.lookup(version, pathParts[1], false); return new LayeringDescriptor(!lookup.getDirectlyContained(), lookup.getAVMStore().getDescriptor(), lookup.getFinalStore().getDescriptor()); } /** * Lookup a directory specifically. * @param version The version to look under. * @param path The path to lookup. * @return A lookup object. */ public Lookup lookupDirectory(int version, String path) { fLookupCount.set(fLookupCount.get() + 1); if (fLookupCount.get() > 50) { throw new AVMCycleException("Cycle in lookup."); } String [] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], false); return store.lookupDirectory(version, pathParts[1], false); } /** * Utility to split a path, foo:bar/baz into its repository and path parts. * @param path The fully qualified path. * @return The repository name and the repository path. */ private String[] SplitPath(String path) { String [] pathParts = path.split(":"); if (pathParts.length != 2) { throw new AVMException("Invalid path: " + path); } return pathParts; } /** * Get the path to file storage. * @return The root path of file storage. */ public String getStorageRoot() { return fStorage; } /** * Make a directory into a primary indirection. * @param path The full path. */ public void makePrimary(String path) { fLookupCount.set(1); String[] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], true); store.makePrimary(pathParts[1]); } /** * Change what a layered directory points at. * @param path The full path to the layered directory. * @param target The new target path. */ public void retargetLayeredDirectory(String path, String target) { fLookupCount.set(1); String[] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], true); store.retargetLayeredDirectory(pathParts[1], target); } /** * Get the history chain for a node. * @param desc The node to get history of. * @param count The maximum number of ancestors to traverse. Negative means all. * @return A List of ancestors. */ public List getHistory(AVMNodeDescriptor desc, int count) { AVMNode node = AVMContext.fgInstance.fAVMNodeDAO.getByID(desc.getId()); if (node == null) { throw new AVMNotFoundException("Not found."); } if (count < 0) { count = Integer.MAX_VALUE; } List history = new ArrayList(); for (int i = 0; i < count; i++) { node = node.getAncestor(); if (node == null) { break; } history.add(node.getDescriptor("UNKNOWN", "UNKNOWN", "UNKNOWN")); } return history; } /** * Set the opacity of a layered directory. An opaque directory hides * the things it points to via indirection. * @param path The path to the layered directory. * @param opacity True is opaque; false is not. */ public void setOpacity(String path, boolean opacity) { fLookupCount.set(1); String[] pathParts = SplitPath(path); AVMStore store = getAVMStoreByName(pathParts[0], true); store.setOpacity(pathParts[1], opacity); } /** * Get the AVMStoreDescriptor for an AVMStore. * @param name The name of the AVMStore. * @return The descriptor. */ public AVMStoreDescriptor getAVMStoreDescriptor(String name) { return getAVMStoreByName(name, false).getDescriptor(); } /** * Get the common ancestor of two nodes if one exists. Unfortunately * this is a quadratic problem, taking time proportional to the product * of the lengths of the left and right history chains. * @param left The first node. * @param right The second node. * @return The common ancestor. There are four possible results. Null means * that there is no common ancestor. Left returned means that left is strictly * an ancestor of right. Right returned means that right is strictly an * ancestor of left. Any other non null return is the common ancestor and * indicates that left and right are in conflict. */ public AVMNodeDescriptor getCommonAncestor(AVMNodeDescriptor left, AVMNodeDescriptor right) { AVMNode lNode = AVMContext.fgInstance.fAVMNodeDAO.getByID(left.getId()); AVMNode rNode = AVMContext.fgInstance.fAVMNodeDAO.getByID(right.getId()); if (lNode == null || rNode == null) { throw new AVMNotFoundException("Node not found."); } List leftHistory = new ArrayList(); while (lNode != null) { leftHistory.add(lNode); lNode = lNode.getAncestor(); } List rightHistory = new ArrayList(); while (rNode != null) { rightHistory.add(rNode); rNode = rNode.getAncestor(); } for (AVMNode l : leftHistory) { for (AVMNode r : rightHistory) { if (l.equals(r)) { return l.getDescriptor("", "", ""); } } } return null; } /** * Get the single instance of AVMRepository. * @return The single instance. */ public static AVMRepository GetInstance() { return fgInstance; } }