Britt Park 9c17cfa1e0 Purge tests cover more now. Moved purging queries into mapping file.
Added call to AVMService to get Versions
by creation date.  GetRepositoryVersions methods now return Lists of 
VersionDescriptors, value objects with the attributes of a version.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/WCM-DEV2/root@3131 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2006-06-17 18:48:44 +00:00

792 lines
26 KiB
Java

/*
* 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.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
/**
* This or Repository are
* the implementors of the operations specified by AVMService.
* @author britt
*/
class SuperRepository
{
/**
* The single instance of SuperRepository.
*/
private static SuperRepository fgInstance;
/**
* The Hibernate Session associated with the current operation.
*/
private ThreadLocal<Session> fSession;
/**
* The current lookup count.
*/
private ThreadLocal<Integer> 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 branchIssuer
* @param layerIssuer
* @param storage
*/
public SuperRepository(Issuer nodeIssuer,
Issuer contentIssuer,
Issuer layerIssuer,
String storage)
{
fStorage = storage;
fNodeIssuer = nodeIssuer;
fContentIssuer = contentIssuer;
fLayerIssuer = layerIssuer;
fSession = new ThreadLocal<Session>();
fLookupCount = new ThreadLocal<Integer>();
fgInstance = this;
}
/**
* Set the (thread local) Hibernate session.
* @param session The Session to set.
*/
public void setSession(Session session)
{
fSession.set(session);
fLookupCount.set(0);
}
/**
* Create a file.
* @param path The path to the containing directory.
* @param name The name for the new file.
* @param source A (possibly null) InputStream with content for the new file.
*/
public OutputStream createFile(String path, String name)
{
fLookupCount.set(1);
String[] pathParts = SplitPath(path);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.UPGRADE);
return rep.createFile(pathParts[1], name);
}
/**
* 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);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.UPGRADE);
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);
Repository rep = getRepositoryByName(pathParts[0]);
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);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.UPGRADE);
rep.createLayeredFile(srcPath, pathParts[1], name);
}
/**
* Create a new repository.
* @param name The name to give the new repository.
*/
public void createRepository(String name)
{
// TODO need to check for repository existence first.
// Newing up the object causes it to be written to the db.
@SuppressWarnings("unused")
Repository rep = new RepositoryImpl(this, name);
// Special handling for repository creation.
// TODO is this needed.
rep.getRoot().setIsNew(false);
}
/**
* 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);
Repository srcRepo = getRepositoryByName(pathParts[0]);
fSession.get().lock(srcRepo, LockMode.READ);
Lookup sPath = srcRepo.lookup(version, pathParts[1]);
// Lookup the destination directory.
fLookupCount.set(1);
pathParts = SplitPath(dstPath);
Repository dstRepo = getRepositoryByName(pathParts[0]);
fSession.get().lock(dstRepo, LockMode.UPGRADE);
Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1]);
// dPath.acquireLocks();
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.addChild(name, dstNode, dPath);
}
/**
* 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);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.UPGRADE);
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);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.UPGRADE);
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);
Repository srcRepo = getRepositoryByName(pathParts[0]);
fSession.get().lock(srcRepo, LockMode.UPGRADE);
Lookup sPath = srcRepo.lookupDirectory(-1, pathParts[1]);
// sPath.acquireLocks();
DirectoryNode srcDir = (DirectoryNode)sPath.getCurrentNode();
AVMNode srcNode = srcDir.lookupChild(sPath, srcName, -1);
if (srcNode == null)
{
throw new AVMNotFoundException("Not found: " + srcName);
}
fLookupCount.set(1);
pathParts = SplitPath(dstPath);
Repository dstRepo = getRepositoryByName(pathParts[0]);
fSession.get().lock(dstRepo, LockMode.UPGRADE);
Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1]);
// dPath.acquireLocks();
DirectoryNode dstDir = (DirectoryNode)dPath.getCurrentNode();
AVMNode dstNode = dstDir.lookupChild(dPath, dstName, -1);
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, sPath);
fLookupCount.set(1);
pathParts = SplitPath(dstPath);
dPath = dstRepo.lookup(-1, pathParts[1]);
// dPath.acquireLocks();
dstDir = (DirectoryNode)dPath.getCurrentNode();
dstNode.setVersionID(dstRepo.getNextVersionID());
dstDir.addChild(dstName, dstNode, dPath);
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);
Repository repo = getRepositoryByName(pathParts[0]);
fSession.get().lock(repo, LockMode.UPGRADE);
repo.uncover(pathParts[1], name);
}
/**
* Snapshot the given repositories.
* @param repositories The list of repository name to snapshot.
*/
public void createSnapshot(List<String> repositories)
{
for (String repName : repositories)
{
Repository repo = getRepositoryByName(repName);
fSession.get().lock(repo, LockMode.UPGRADE);
repo.createSnapshot();
}
}
/**
* Create a snapshot of a single repository.
* @param repository The name of the repository.
*/
public void createSnapshot(String repository)
{
Repository repo = getRepositoryByName(repository);
fSession.get().lock(repo, LockMode.UPGRADE);
repo.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);
Repository repo = getRepositoryByName(pathParts[0]);
fSession.get().lock(repo, LockMode.UPGRADE);
repo.removeNode(pathParts[1], name);
}
/**
* Get rid of all content that lives only in the given repository.
* Also removes the repository.
* @param name The name of the repository to purge.
*/
@SuppressWarnings("unchecked")
public void purgeRepository(String name)
{
Repository rep = getRepositoryByName(name);
fSession.get().lock(rep, LockMode.UPGRADE);
AVMNode root = rep.getRoot();
root.setIsRoot(false);
Query query = fSession.get().createQuery("from VersionRootImpl vr where vr.repository = :rep");
query.setEntity("rep", rep);
List<VersionRoot> vRoots = (List<VersionRoot>)query.list();
for (VersionRoot vr : vRoots)
{
AVMNode node = vr.getRoot();
node.setIsRoot(false);
fSession.get().delete(vr);
}
query = fSession.get().createQuery("from AVMNodeImpl an where an.repository = :rep");
query.setEntity("rep", rep);
Iterator<AVMNode> iter = (Iterator<AVMNode>)query.iterate();
while (iter.hasNext())
{
AVMNode node = iter.next();
node.setRepository(null);
}
fSession.get().flush();
fSession.get().delete(rep);
}
/**
* Remove all content specific to a repository and version.
* @param name The name of the repository.
* @param version The version to purge.
*/
public void purgeVersion(String name, int version)
{
Repository rep = getRepositoryByName(name);
fSession.get().lock(rep, LockMode.UPGRADE);
rep.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);
Repository repo = getRepositoryByName(pathParts[0]);
fSession.get().lock(repo, LockMode.READ);
return repo.getInputStream(version, pathParts[1]);
}
/**
* 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 List<FolderEntry> getListing(int version, String path)
{
fLookupCount.set(1);
String [] pathParts = SplitPath(path);
Repository repo = getRepositoryByName(pathParts[0]);
fSession.get().lock(repo, LockMode.READ);
return repo.getListing(version, pathParts[1]);
}
/**
* Get a directory listing from a directory node descriptor.
* @param dir The directory node descriptor.
* @return
*/
public Map<String, AVMNodeDescriptor> getListing(AVMNodeDescriptor dir)
{
fLookupCount.set(1);
AVMNode node = (AVMNode)fSession.get().get(AVMNodeImpl.class, 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 the names of all repositories.
* @return A list of names.
*/
@SuppressWarnings("unchecked")
public List<String> getRepositoryNames()
{
Query query = fSession.get().createQuery("select r.name from RepositoryImpl r");
return (List<String>)query.list();
}
/**
* Get all version for a given repository.
* @param name The name of the repository.
* @return A Set will all the version ids.
*/
public List<VersionDescriptor> getRepositoryVersions(String name)
{
Repository rep = getRepositoryByName(name);
fSession.get().lock(rep, LockMode.READ);
return rep.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 repository.
* @param from The earliest date.
* @param to The latest date.
* @return The Set of version IDs.
*/
public List<VersionDescriptor> getRepositoryVersions(String name, Date from, Date to)
{
Repository rep = getRepositoryByName(name);
fSession.get().lock(rep, LockMode.READ);
return rep.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 (thread local) Hibernate session.
* @return The Session.
*/
public Session getSession()
{
return fSession.get();
}
/**
* 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);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.READ);
return rep.getIndirectionPath(version, pathParts[1]);
}
/**
* Get the next version id for the given repository.
* @param name The name of the repository.
* @return The next version id.
*/
public int getLatestVersionID(String name)
{
Repository rep = getRepositoryByName(name);
return rep.getNextVersionID();
}
/**
* Get a repository by name.
* @param name The name of the repository.
* @return The Repository.
*/
private Repository getRepositoryByName(String name)
{
return (Repository)fSession.get().get(RepositoryImpl.class, name);
}
/**
* Get a descriptor for a repository root.
* @param version The version to get.
* @param name The name of the repository.
* @return The descriptor for the root.
*/
public AVMNodeDescriptor getRepositoryRoot(int version, String name)
{
Repository rep = getRepositoryByName(name);
if (rep == null)
{
throw new AVMNotFoundException("Not found: " + name);
}
fSession.get().lock(rep, LockMode.READ);
return rep.getRoot(version);
}
/**
* 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)
{
fLookupCount.set(fLookupCount.get() + 1);
if (fLookupCount.get() > 10)
{
throw new AVMCycleException("Cycle in lookup.");
}
String [] pathParts = SplitPath(path);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.READ);
return rep.lookup(version, pathParts[1]);
}
/**
* 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 = (AVMNode)fSession.get().get(AVMNodeImpl.class, 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);
}
/**
* 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() > 10)
{
throw new AVMCycleException("Cycle in lookup.");
}
String [] pathParts = SplitPath(path);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.READ);
return rep.lookupDirectory(version, pathParts[1]);
}
/**
* 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);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.UPGRADE);
rep.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);
Repository rep = getRepositoryByName(pathParts[0]);
fSession.get().lock(rep, LockMode.UPGRADE);
rep.retargetLayeredDirectory(pathParts[1], target);
}
/**
* Get the single instance of SuperRepository.
* @return
*/
public static SuperRepository GetInstance()
{
return fgInstance;
}
}