From 936f6d7021902805371b10812b01261bc5c37385 Mon Sep 17 00:00:00 2001 From: Britt Park Date: Fri, 16 Jun 2006 16:59:15 +0000 Subject: [PATCH] This checks in the current state of WCM development. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/WCM-DEV2/root@3128 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../public-services-security-context.xml | 6 +- source/java/ehcache.xml | 131 +- source/java/hibernate.cfg.xml | 29 +- source/java/log4j.properties | 2 +- .../repo/avm/AVMBadArgumentException.java | 68 + .../alfresco/repo/avm/AVMCycleException.java | 67 + .../org/alfresco/repo/avm/AVMException.java | 65 + .../alfresco/repo/avm/AVMExistsException.java | 67 + .../java/org/alfresco/repo/avm/AVMNode.java | 257 +--- .../alfresco/repo/avm/AVMNodeDescriptor.java | 366 +++++ .../org/alfresco/repo/avm/AVMNodeImpl.java | 360 +++++ .../repo/avm/AVMNotFoundException.java | 67 + .../org/alfresco/repo/avm/AVMService.java | 91 +- .../org/alfresco/repo/avm/AVMServiceImpl.java | 755 ++++++++++ .../alfresco/repo/avm/AVMServicePerfTest.java | 155 +++ .../org/alfresco/repo/avm/AVMServiceTest.java | 1236 +++++++++++++---- .../alfresco/repo/avm/AVMServiceTestBase.java | 168 +++ .../org/alfresco/repo/avm/AVMStressTest.java | 106 ++ .../java/org/alfresco/repo/avm/AVMTester.java | 610 ++++++++ .../repo/avm/AVMWrongTypeException.java | 68 + .../alfresco/repo/avm/BasicAttributes.java | 83 ++ .../repo/avm/BasicAttributesImpl.java | 200 +++ .../org/alfresco/repo/avm/ChildEntry.java | 61 + .../org/alfresco/repo/avm/ChildEntryImpl.java | 148 ++ .../org/alfresco/repo/avm/DeletedChild.java | 37 + .../alfresco/repo/avm/DeletedChildImpl.java | 123 ++ .../org/alfresco/repo/avm/DirectoryNode.java | 77 +- .../alfresco/repo/avm/DirectoryNodeImpl.java | 100 ++ .../org/alfresco/repo/avm/FileContent.java | 195 +-- .../alfresco/repo/avm/FileContentImpl.java | 325 +++++ .../java/org/alfresco/repo/avm/FileNode.java | 15 +- .../org/alfresco/repo/avm/FileNodeImpl.java | 42 + source/java/org/alfresco/repo/avm/Issuer.java | 149 ++ .../org/alfresco/repo/avm/IssuerTest.java | 41 + .../java/org/alfresco/repo/avm/Layered.java | 9 +- .../repo/avm/LayeredDirectoryNode.java | 499 +------ .../repo/avm/LayeredDirectoryNodeImpl.java | 749 ++++++++++ .../alfresco/repo/avm/LayeredFileNode.java | 196 +-- .../repo/avm/LayeredFileNodeImpl.java | 213 +++ source/java/org/alfresco/repo/avm/Lookup.java | 132 +- .../alfresco/repo/avm/LookupComponent.java | 59 +- .../org/alfresco/repo/avm/OrphanReaper.java | 125 ++ .../alfresco/repo/avm/PlainDirectoryNode.java | 263 +--- .../repo/avm/PlainDirectoryNodeImpl.java | 379 +++++ .../org/alfresco/repo/avm/PlainFileNode.java | 200 +-- .../alfresco/repo/avm/PlainFileNodeImpl.java | 212 +++ .../org/alfresco/repo/avm/Repository.java | 197 +-- .../org/alfresco/repo/avm/RepositoryImpl.java | 732 ++++++++++ .../alfresco/repo/avm/SuperRepository.java | 850 +++++++++--- .../org/alfresco/repo/avm/VersionRoot.java | 86 ++ .../alfresco/repo/avm/VersionRootImpl.java | 188 +++ .../alfresco/repo/avm/hibernate/AVM.hbm.xml | 250 ++-- .../repo/avm/hibernate/HibernateTxn.java | 124 +- .../repo/avm/impl/AVMServiceImpl.java | 484 ------- .../repo/avm/impl/RepositoryImpl.java | 493 ------- .../repo/avm/impl/SuperRepositoryImpl.java | 571 -------- .../org/alfresco/repo/avm/util/BulkLoad.java | 109 ++ 57 files changed, 9563 insertions(+), 3827 deletions(-) create mode 100644 source/java/org/alfresco/repo/avm/AVMBadArgumentException.java create mode 100644 source/java/org/alfresco/repo/avm/AVMCycleException.java create mode 100644 source/java/org/alfresco/repo/avm/AVMException.java create mode 100644 source/java/org/alfresco/repo/avm/AVMExistsException.java create mode 100644 source/java/org/alfresco/repo/avm/AVMNodeDescriptor.java create mode 100644 source/java/org/alfresco/repo/avm/AVMNodeImpl.java create mode 100644 source/java/org/alfresco/repo/avm/AVMNotFoundException.java create mode 100644 source/java/org/alfresco/repo/avm/AVMServiceImpl.java create mode 100644 source/java/org/alfresco/repo/avm/AVMServicePerfTest.java create mode 100644 source/java/org/alfresco/repo/avm/AVMServiceTestBase.java create mode 100644 source/java/org/alfresco/repo/avm/AVMStressTest.java create mode 100644 source/java/org/alfresco/repo/avm/AVMTester.java create mode 100644 source/java/org/alfresco/repo/avm/AVMWrongTypeException.java create mode 100644 source/java/org/alfresco/repo/avm/BasicAttributes.java create mode 100644 source/java/org/alfresco/repo/avm/BasicAttributesImpl.java create mode 100644 source/java/org/alfresco/repo/avm/ChildEntry.java create mode 100644 source/java/org/alfresco/repo/avm/ChildEntryImpl.java create mode 100644 source/java/org/alfresco/repo/avm/DeletedChild.java create mode 100644 source/java/org/alfresco/repo/avm/DeletedChildImpl.java create mode 100644 source/java/org/alfresco/repo/avm/DirectoryNodeImpl.java create mode 100644 source/java/org/alfresco/repo/avm/FileContentImpl.java create mode 100644 source/java/org/alfresco/repo/avm/FileNodeImpl.java create mode 100644 source/java/org/alfresco/repo/avm/Issuer.java create mode 100644 source/java/org/alfresco/repo/avm/IssuerTest.java create mode 100644 source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java create mode 100644 source/java/org/alfresco/repo/avm/LayeredFileNodeImpl.java create mode 100644 source/java/org/alfresco/repo/avm/OrphanReaper.java create mode 100644 source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java create mode 100644 source/java/org/alfresco/repo/avm/PlainFileNodeImpl.java create mode 100644 source/java/org/alfresco/repo/avm/RepositoryImpl.java create mode 100644 source/java/org/alfresco/repo/avm/VersionRoot.java create mode 100644 source/java/org/alfresco/repo/avm/VersionRootImpl.java delete mode 100644 source/java/org/alfresco/repo/avm/impl/AVMServiceImpl.java delete mode 100644 source/java/org/alfresco/repo/avm/impl/RepositoryImpl.java delete mode 100644 source/java/org/alfresco/repo/avm/impl/SuperRepositoryImpl.java create mode 100644 source/java/org/alfresco/repo/avm/util/BulkLoad.java diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index f0a67b5db6..a5d42fc09f 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -43,8 +43,7 @@ - - + @@ -449,7 +448,7 @@ - + @@ -461,6 +460,7 @@ org.alfresco.service.cmr.lock.LockService.getLockStatus=ACL_NODE.0.sys:base.Read org.alfresco.service.cmr.lock.LockService.getLockType=ACL_NODE.0.sys:base.Read org.alfresco.service.cmr.lock.LockService.checkForLock=ACL_NODE.0.sys:base.Read + org.alfresco.service.cmr.lock.LockService.getLocks=ACL_NODE.0.sys:base.Read diff --git a/source/java/ehcache.xml b/source/java/ehcache.xml index 7350e4c0c2..3102987c91 100644 --- a/source/java/ehcache.xml +++ b/source/java/ehcache.xml @@ -3,87 +3,96 @@ - - - - + + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/source/java/hibernate.cfg.xml b/source/java/hibernate.cfg.xml index 444ff3c9b9..f19d30fcf1 100644 --- a/source/java/hibernate.cfg.xml +++ b/source/java/hibernate.cfg.xml @@ -3,22 +3,35 @@ "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd" > - root + + root jdbc:mysql://127.0.0.1/avm com.mysql.jdbc.Driver thread - org.hibernate.dialect.MySQLInnoDBDialect + org.hibernate.dialect.MySQLInnoDBDialect + false false 2 - 5 - 20 - 900 - 500 + + 16 + 16 + 60 + 0 16 - true - false + false + 0 + true + true diff --git a/source/java/log4j.properties b/source/java/log4j.properties index d19d8b027a..b25d591fb5 100644 --- a/source/java/log4j.properties +++ b/source/java/log4j.properties @@ -6,4 +6,4 @@ log4j.appender.stdout.layout.ConversionPattern=%d %5p %c{1}:%m%n ### Set log levels. log4j.rootLogger=warn, stdout -log4j.logger.org.hibernate=info +log4j.logger.org.hibernate=fatal \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/AVMBadArgumentException.java b/source/java/org/alfresco/repo/avm/AVMBadArgumentException.java new file mode 100644 index 0000000000..bfe4515378 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMBadArgumentException.java @@ -0,0 +1,68 @@ +/* + * 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; + +/** + * This is thrown when bad or illegal arguments are passed. + * @author britt + */ +public class AVMBadArgumentException extends AVMException +{ + private static final long serialVersionUID = -3651428546518806565L; + + /** + * @param msgId + */ + public AVMBadArgumentException(String msgId) + { + super(msgId); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + */ + public AVMBadArgumentException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param cause + */ + public AVMBadArgumentException(String msgId, Throwable cause) + { + super(msgId, cause); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public AVMBadArgumentException(String msgId, Object[] msgParams, + Throwable cause) + { + super(msgId, msgParams, cause); + // TODO Auto-generated constructor stub + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMCycleException.java b/source/java/org/alfresco/repo/avm/AVMCycleException.java new file mode 100644 index 0000000000..34fa31a001 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMCycleException.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * This is thrown when a cycle is detected during lookup. + * @author britt + */ +public class AVMCycleException extends AVMException +{ + private static final long serialVersionUID = -7390775107865356648L; + + /** + * @param msgId + */ + public AVMCycleException(String msgId) + { + super(msgId); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + */ + public AVMCycleException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param cause + */ + public AVMCycleException(String msgId, Throwable cause) + { + super(msgId, cause); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public AVMCycleException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + // TODO Auto-generated constructor stub + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMException.java b/source/java/org/alfresco/repo/avm/AVMException.java new file mode 100644 index 0000000000..f6dc4b8c1c --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMException.java @@ -0,0 +1,65 @@ +/* + * 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 org.alfresco.error.AlfrescoRuntimeException; + +/** + * Class for generic AVM Exceptions. + * @author britt + */ +public class AVMException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -4894449240293309025L; + + /** + * @param msgId + */ + public AVMException(String msgId) + { + super(msgId); + } + + /** + * @param msgId + * @param msgParams + */ + public AVMException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + /** + * @param msgId + * @param cause + */ + public AVMException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public AVMException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMExistsException.java b/source/java/org/alfresco/repo/avm/AVMExistsException.java new file mode 100644 index 0000000000..6bdbb9f39a --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMExistsException.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * This is thrown when a node exists and shouldn't. + * @author britt + */ +public class AVMExistsException extends AVMException +{ + private static final long serialVersionUID = -5079803858722700975L; + + /** + * @param msgId + */ + public AVMExistsException(String msgId) + { + super(msgId); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + */ + public AVMExistsException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param cause + */ + public AVMExistsException(String msgId, Throwable cause) + { + super(msgId, cause); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public AVMExistsException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + // TODO Auto-generated constructor stub + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMNode.java b/source/java/org/alfresco/repo/avm/AVMNode.java index 855f80fd82..066d33b92f 100644 --- a/source/java/org/alfresco/repo/avm/AVMNode.java +++ b/source/java/org/alfresco/repo/avm/AVMNode.java @@ -14,256 +14,137 @@ * language governing permissions and limitations under the * License. */ - package org.alfresco.repo.avm; -import org.alfresco.repo.avm.hibernate.AVMNodeBean; -import org.alfresco.repo.avm.hibernate.DirectoryNodeBean; - /** - * Base class for all repository file system like objects. + * The Interface for versionable objects. * @author britt */ -public abstract class AVMNode +interface AVMNode { - /** - * The AVMNodeBean that contains our data. - */ - private AVMNodeBean fData; - - /** - * Default constructor. - */ - protected AVMNode() - { - fData = null; - } - /** * Set the ancestor of this node. * @param ancestor The ancestor to set. */ - public void setAncestor(AVMNode ancestor) - { - fData.setAncestor(ancestor.getDataBean()); - } + public void setAncestor(AVMNode ancestor); /** * Get the ancestor of this node. * @return The ancestor of this node. */ - public AVMNode getAncestor() - { - return AVMNodeFactory.CreateFromBean(fData.getAncestor()); - } - + public AVMNode getAncestor(); + /** * Set the merged from node. * @param mergedFrom The merged from node. */ - public void setMergedFrom(AVMNode mergedFrom) - { - fData.setMergedFrom(mergedFrom.getDataBean()); - } - + public void setMergedFrom(AVMNode mergedFrom); + /** * Get the node this was merged from. * @return The node this was merged from. */ - public AVMNode getMergedFrom() - { - return AVMNodeFactory.CreateFromBean(fData.getMergedFrom()); - } - - /** - * Should this be copied on modification. - */ - public boolean shouldBeCopied() - { - return !fData.getIsNew(); - } - - /** - * Set to need copying or not. - * @param copyable Whether this should be copied. - */ - public void setShouldBeCopied(boolean copyable) - { - fData.setIsNew(!copyable); - } - + public AVMNode getMergedFrom(); + /** * Get the version number. * @return The version number. */ - public long getVersion() - { - return fData.getVersionID(); - } - + public int getVersionID(); + /** * Set the version number. * @param version The version number to set. */ - public void setVersion(int version) - { - fData.setVersionID(version); - } - - /** - * Get the branch id of this node. - * @return The branch id. - */ - public long getBranchID() - { - return fData.getBranchID(); - } - - /** - * Set the branch id on this node. - * @param branchID The id to set. - */ - public void setBranchID(long branchID) - { - fData.setBranchID(branchID); - } - + public void setVersionID(int version); + /** * Get the (possibly null) parent. * @return The parent or null. */ - public DirectoryNode getParent() - { - return (DirectoryNode)AVMNodeFactory.CreateFromBean(fData.getParent()); - } + public DirectoryNode getParent(); /** * Set the parent of this node. * @param parent The DirectoryNode to set. */ - public void setParent(DirectoryNode parent) - { - fData.setParent((DirectoryNodeBean)parent.getDataBean()); - } - + public void setParent(DirectoryNode parent); + /** * Perform a COW if required. * @param lPath The lookup path. * @return A 'copied' version of this node. */ - public AVMNode copyOnWrite(Lookup lPath) - { - // Call the subclass's copy on write logic. - AVMNode newMe = possiblyCopy(lPath); - // No copying needed, so short circuit. - if (newMe == null) - { - return this; - } - String myName = lPath.getName(); - lPath.upCurrentNode(); - Repository repos = lPath.getRepository(); - newMe.setVersion(repos.getLatestVersion() + 1); - // Get our parent directory if we have one. - DirectoryNode parent = null; - if (getParent() != null) - { - parent = (DirectoryNode)lPath.getCurrentNode(); - } - if (parent != null) - { - // Recursive invocation. - DirectoryNode newParent = - (DirectoryNode)parent.copyOnWrite(lPath); - newParent.putChild(myName, newMe); - newMe.setParent(newParent); - } - else // Null parent means root of repository. - { - repos.setNewRoot((DirectoryNode)newMe); - } - newMe.setRepository(repos); - newMe.setShouldBeCopied(false); - repos.setNew(newMe); - return newMe; - } + public AVMNode copyOnWrite(Lookup lPath); /** * Possibly copy ourselves. * @param lPath The Lookup for this node. * @return A copy of ourself or null if no copy was necessary. */ - public abstract AVMNode possiblyCopy(Lookup lPath); - - /** - * Handle any after recursive copy processing. - * - * @param parent The DirectoryNode that is the parent of - * this copied node, after recursive copying. - */ - public abstract void handlePostCopy(DirectoryNode parent); - + public AVMNode possiblyCopy(Lookup lPath); + /** * Set the repository for a node. * @param repo The Repository to set. */ - public void setRepository(Repository repo) - { - fData.setRepository(repo.getDataBean()); - } + public void setRepository(Repository repo); - /** - * Get the data bean in this node. - * @return The data bean. - */ - public AVMNodeBean getDataBean() - { - return fData; - } - - /** - * Set the data bean in this node. - * @param bean The data bean to set. - */ - public void setDataBean(AVMNodeBean bean) - { - fData = bean; - } - /** * Get the type of this node. */ - public abstract int getType(); + public int getType(); + + /** + * Get the descriptor for this node. + * @param The Lookup. + * @return The descriptor for this node. + */ + public AVMNodeDescriptor getDescriptor(Lookup lPath); /** - * Get a debugging string representation. + * Get a node descriptor for this node. + * @param parentPath The parent path. + * @param name The name looked up as. + * @param parentIndirection The indirection of the parent. + * @return The descriptor for this node. + */ + public AVMNodeDescriptor getDescriptor(String parentPath, String name, String parentIndirection); + + /** + * Get the object id. + * @return The object id. + */ + public long getId(); + + /** + * Set this node's newness. + * @param isNew The newness. + */ + public void setIsNew(boolean isNew); + + /** + * Get the newnews. + * @return Whether the node is new. + */ + public boolean getIsNew(); + + /** + * Get a string representation for debugging. * @param lPath The Lookup. + * @return A String representation. */ - public abstract String toString(Lookup lPath); + public String toString(Lookup lPath); + + /** + * Set whether this node to be a root of a Repository. + * @param isRoot + */ + public void setIsRoot(boolean isRoot); - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) + /** + * Get whether this node is a root of a Repository. + * @return Whether this node is a root. */ - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (!(obj instanceof AVMNode)) - { - return false; - } - return getDataBean().equals(((AVMNode)obj).getDataBean()); - } - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() - { - return getDataBean().hashCode(); - } -} + public boolean getIsRoot(); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/AVMNodeDescriptor.java b/source/java/org/alfresco/repo/avm/AVMNodeDescriptor.java new file mode 100644 index 0000000000..50aa9dd5bb --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMNodeDescriptor.java @@ -0,0 +1,366 @@ +/* + * 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; + +/** + * This class describes an AVM node object. + * It serves a similar purpose to the data structure + * returned by the stat() system call in UNIX. + * + * @author britt + */ +public class AVMNodeDescriptor +{ + /** + * The path that this was looked up with. + */ + private String fPath; + + /** + * The type of this node. AVMNodeType constants. + */ + private int fType; + + /** + * The Owner. + */ + private String fOwner; + + /** + * The Creator. + */ + private String fCreator; + + /** + * The last modifier. + */ + private String fLastModifier; + + /** + * The Create date. + */ + private long fCreateDate; + + /** + * The Modification date. + */ + private long fModDate; + + /** + * The Access date. + */ + private long fAccessDate; + + /** + * The object id. + */ + private long fID; + + /** + * The version number. + */ + private int fVersionID; + + /** + * The indirection if this is a layer. + */ + private String fIndirection; + + /** + * Is this a primary indirection node. + */ + private boolean fIsPrimary; + + /** + * The layer id or -1 if this is not a layered node. + */ + private long fLayerID; + + /** + * The length, if this is a file or -1 otherwise. + */ + private long fLength; + + /** + * Make one up. + * @param path The looked up path. + * @param type The type of the node. + * @param creator The creator of the node. + * @param owner The owner of the node. + * @param lastModifier The last modifier of the node. + * @param createDate The creation date. + * @param modDate The modification date. + * @param accessDate The access date. + * @param id The object id. + * @param versionID The version id. + * @param indirection The indirection. + * @param isPrimary Whether this is a primary indirection. + * @param layerID The layer id. + * @param length The file length. + */ + public AVMNodeDescriptor(String path, + int type, + String creator, + String owner, + String lastModifier, + long createDate, + long modDate, + long accessDate, + long id, + int versionID, + String indirection, + boolean isPrimary, + long layerID, + long length) + { + fPath = path; + fType = type; + fCreator = creator; + fOwner = owner; + fLastModifier = lastModifier; + fCreateDate = createDate; + fModDate = modDate; + fAccessDate = accessDate; + fID = id; + fVersionID = versionID; + fIndirection = indirection; + fIsPrimary = isPrimary; + fLayerID = layerID; + fLength = length; + } + + /** + * Get the last access date in java milliseconds. + * @return The last access date. + */ + public long getAccessDate() + { + return fAccessDate; + } + + /** + * Get the creation date in java milliseconds. + * @return The creation date. + */ + public long getCreateDate() + { + return fCreateDate; + } + + /** + * Get the user who created this. + * @return The creator. + */ + public String getCreator() + { + return fCreator; + } + + /** + * Get the indirection path if this is layered or null. + * @return The indirection path or null. + */ + public String getIndirection() + { + return fIndirection; + } + + /** + * Is this a primary indirection node. Will always + * be false for non-layered nodes. + * @return Whether this is a primary indirection node. + */ + public boolean isPrimary() + { + return fIsPrimary; + } + + /** + * Determines whether this node corresponds to + * either a plain or layered file. + * + * @return true if AVMNodeDescriptor is a plain or layered file, + * otherwise false. + */ + public boolean isFile() + { + return ( fType == AVMNodeType.PLAIN_FILE || + fType == AVMNodeType.LAYERED_FILE + ); + } + + /** + * Determines whether this node corresponds to + * a plain (non-layered) file. + * + * @return true if AVMNodeDescriptor is a plain file, otherwise false. + */ + public boolean isPlainFile() + { + return (fType == AVMNodeType.PLAIN_FILE); + } + + /** + * Determines whether this node corresponds to + * a layered file. + * + * @return true if AVMNodeDescriptor is a layered file, + * otherwise false. + */ + public boolean isLayeredFile() + { + return (fType == AVMNodeType.LAYERED_FILE); + } + + /** + * Determines whether this node corresponds to + * either a plain or layered directory. + * + * @return true if AVMNodeDescriptor is a plain or layered directory, + * otherwise false. + */ + public boolean isDirectory() + { + return ( fType == AVMNodeType.PLAIN_DIRECTORY || + fType == AVMNodeType.LAYERED_DIRECTORY + ); + } + + /** + * Determines whether this node corresponds to + * a plain (non-layered) directory. + * + * @return true if AVMNodeDescriptor is a plain directory, otherwise false. + */ + public boolean isPlainDirectory() + { + return (fType == AVMNodeType.PLAIN_DIRECTORY ); + } + + /** + * Determines whether this node corresponds to + * a layered directory. + * + * @return true if AVMNodeDescriptor is a layered directory, + * otherwise false. + */ + public boolean isLayeredDirectory() + { + return (fType == AVMNodeType.LAYERED_DIRECTORY ); + } + + + /** + * Get the user who last modified this node. + * @return Who last modified this node. + */ + public String getLastModifier() + { + return fLastModifier; + } + + /** + * Get the layer id of this node. + * @return + */ + public long getLayerID() + { + return fLayerID; + } + + /** + * Get the modification date of this node. + * @return The modification date. + */ + public long getModDate() + { + return fModDate; + } + + /** + * Get the owner of this node. + * @return The owner of this node. + */ + public String getOwner() + { + return fOwner; + } + + /** + * Get the path that this node was looked up by. + * @return The path by which this was looked up. + */ + public String getPath() + { + return fPath; + } + + /** + * Get the type of this node. AVMNodeType constants. + * @return The type node. + */ + public int getType() + { + return fType; + } + + /** + * Get the version id of this node. + * @return The version id of this node. + */ + public int getVersionID() + { + return fVersionID; + } + + /** + * Get the object id. + * @return The object id. + */ + public long getId() + { + return fID; + } + + public long getLength() + { + return fLength; + } + + /** + * Get a debuggable string representation of this. + * @return A string representation of this. + */ + @Override + public String toString() + { + switch (fType) + { + case AVMNodeType.PLAIN_FILE : + return "[PF:" + fID + "]"; + case AVMNodeType.PLAIN_DIRECTORY : + return "[PD:" + fID + "]"; + case AVMNodeType.LAYERED_FILE : + return "[LF:" + fID + ":" + fIndirection + "]"; + case AVMNodeType.LAYERED_DIRECTORY : + return "[LD:" + fID + ":" + fIndirection + "]"; + default : + throw new AVMException("Internal Error."); + } + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMNodeImpl.java b/source/java/org/alfresco/repo/avm/AVMNodeImpl.java new file mode 100644 index 0000000000..85e21c2a3e --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMNodeImpl.java @@ -0,0 +1,360 @@ +/* + * 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.Serializable; + +/** + * Base class for all repository file system like objects. + * @author britt + */ +abstract class AVMNodeImpl implements AVMNode, Serializable +{ + /** + * The Object ID. + */ + private long fID; + + /** + * The Version ID. + */ + private int fVersionID; + + /** + * The ancestor of this. + */ + private AVMNode fAncestor; + + /** + * The node that was merged into this. + */ + private AVMNode fMergedFrom; + + /** + * The parent of this. + */ + private DirectoryNode fParent; + + /** + * The Repository that owns this. + */ + private Repository fRepository; + + /** + * The basic attributes of this. Owner, creator, mod time, etc. + */ + private BasicAttributes fBasicAttributes; + + /** + * Whether this node is new (and should therefore not be COWed). + */ + private boolean fIsNew; + + /** + * The version number (for concurrency control). + */ + private long fVers; + + /** + * The rootness of this node. + */ + private boolean fIsRoot; + + /** + * Default constructor. + */ + protected AVMNodeImpl() + { + } + + /** + * Constructor used when creating a new concrete subclass instance. + * @param id The object id. + * @param repo The Repository that owns this. + */ + protected AVMNodeImpl(long id, + Repository repo) + { + fID = id; + fVersionID = -1; + fAncestor = null; + fMergedFrom = null; + fParent = null; + fRepository = repo; + fIsRoot = false; + long time = System.currentTimeMillis(); + fBasicAttributes = new BasicAttributesImpl("britt", + "britt", + "britt", + time, + time, + time); + fIsNew = true; + } + + /** + * Set the ancestor of this node. + * @param ancector The ancestor to set. + */ + public void setAncestor(AVMNode ancestor) + { + fAncestor = ancestor; + } + + /** + * Get the ancestor of this node. + * @return The ancestor of this node. + */ + public AVMNode getAncestor() + { + return fAncestor; + } + + /** + * Set the node that was merged into this. + * @param mergedFrom The node that was merged into this. + */ + public void setMergedFrom(AVMNode mergedFrom) + { + fMergedFrom = mergedFrom; + } + + /** + * Get the node that was merged into this. + * @return The node that was merged into this. + */ + public AVMNode getMergedFrom() + { + return fMergedFrom; + } + + /** + * Get the canonical parent of this node. This parent + * is the one under which this node was created. + * @return The canonical parent. + */ + public DirectoryNode getParent() + { + return fParent; + } + + /** + * Set the canonical parent of this node. + * @param parent The canonical parent. + */ + public void setParent(DirectoryNode parent) + { + fParent = parent; + } + + /** + * Perform a copy on write on this node and recursively + * up to the repository root. This is a template method + * which farms out work to possiblyCopy(). + * @param lPath The Lookup. + */ + public AVMNode copyOnWrite(Lookup lPath) + { + // Call the subclass's copy on write logic. + AVMNode newMe = possiblyCopy(lPath); + // No copying needed, so short circuit. + if (newMe == null) + { + return this; + } + String myName = lPath.getName(); + lPath.upCurrentNode(); + Repository repos = lPath.getRepository(); + newMe.setVersionID(repos.getNextVersionID()); + // Get our parent directory if we have one. + DirectoryNode parent = null; + if (getParent() != null) + { + parent = (DirectoryNode)lPath.getCurrentNode(); + } + if (parent != null) + { + // Recursive invocation. + DirectoryNode newParent = + (DirectoryNode)parent.copyOnWrite(lPath); + newParent.putChild(myName, newMe); + newMe.setParent(newParent); + } + else // Null parent means root of repository. + { + repos.setNewRoot((DirectoryNode)newMe); + } + newMe.setRepository(repos); + newMe.setIsNew(true); + return newMe; + } + + /** + * Set the owning repository for this. + * @param repo The owning repository. + */ + public void setRepository(Repository repo) + { + fRepository = repo; + } + + /** + * Get the repository that owns this. + * @return The repository. + */ + public Repository getRepository() + { + return fRepository; + } + + /** + * Equality based on object ids. + * @param obj The thing to compare against. + * @return Equality. + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof AVMNode)) + { + System.err.println("Failing AVMNodeImpl.equals"); + return false; + } + return fID == ((AVMNode)obj).getId(); + } + + /** + * Get a reasonable hash value. + * @return The hash code. + */ + @Override + public int hashCode() + { + return (int)fID; + } + + /** + * Set the object id. For Hibernate. + * @param id The id to set. + */ + protected void setId(long id) + { + fID = id; + } + + /** + * Get the id of this node. + * @return The object id. + */ + public long getId() + { + return fID; + } + + /** + * Set the versionID for this node. + * @param versionID The id to set. + */ + public void setVersionID(int versionID) + { + fVersionID = versionID; + } + + /** + * Get the version id of this node. + * @return The version id. + */ + public int getVersionID() + { + return fVersionID; + } + + /** + * Set the basic attributes. For Hibernate. + * @param attrs + */ + protected void setBasicAttributes(BasicAttributes attrs) + { + fBasicAttributes = attrs; + } + + /** + * Get the basic attributes. + * @return The basic attributes. + */ + public BasicAttributes getBasicAttributes() + { + return fBasicAttributes; + } + + /** + * Set whether this is new. + * @param isNew Whether this is new. + */ + public void setIsNew(boolean isNew) + { + fIsNew = isNew; + } + + /** + * Get whether this is a new node. + * @return Whether this is new. + */ + public boolean getIsNew() + { + return fIsNew; + } + + /** + * Set the version for concurrency control + * @param vers + */ + protected void setVers(long vers) + { + fVers = vers; + } + + /** + * Get the version for concurrency control. + * @return + */ + protected long getVers() + { + return fVers; + } + + /** + * @return + */ + public boolean getIsRoot() + { + return fIsRoot; + } + + /** + * @param isRoot + */ + public void setIsRoot(boolean isRoot) + { + fIsRoot = isRoot; + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMNotFoundException.java b/source/java/org/alfresco/repo/avm/AVMNotFoundException.java new file mode 100644 index 0000000000..1cd760e20f --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMNotFoundException.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * This is the exception thrown when a node is not found. + * @author britt + */ +public class AVMNotFoundException extends AVMException +{ + private static final long serialVersionUID = -8131080195448129281L; + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public AVMNotFoundException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + */ + public AVMNotFoundException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param cause + */ + public AVMNotFoundException(String msgId, Throwable cause) + { + super(msgId, cause); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + */ + public AVMNotFoundException(String msgId) + { + super(msgId); + // TODO Auto-generated constructor stub + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMService.java b/source/java/org/alfresco/repo/avm/AVMService.java index c079277b5b..3610ae9461 100644 --- a/source/java/org/alfresco/repo/avm/AVMService.java +++ b/source/java/org/alfresco/repo/avm/AVMService.java @@ -19,7 +19,9 @@ package org.alfresco.repo.avm; import java.io.InputStream; import java.io.OutputStream; +import java.io.RandomAccessFile; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -27,6 +29,7 @@ import java.util.Set; * Model. It specifies methods that are close in functionality to the underlying * implementation, and is intended as both a first class Alfresco service and an * aid in creating new implementations of existing services. + * Paths are of the form repositoryname:/foo/bar/baz * @author britt */ public interface AVMService @@ -40,11 +43,20 @@ public interface AVMService public InputStream getFileInputStream(int version, String path); /** - * Get an output stream to a file node. + * Get an output stream to a file node. The file must already exist. * @param path The simple absolute path to the file node. */ public OutputStream getFileOutputStream(String path); + /** + * Get a random access file to the given path. + * @param version The version to find. + * @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); + /** * Get a listing of a Folder by name. * @param version The version id to look in. @@ -54,14 +66,22 @@ public interface AVMService public List getDirectoryListing(int version, String path); /** - * Create a new File. - * @param parent The simple absolute path to the parent. - * @param name The name to give the file. + * Get a directory listing from a node descriptor. + * @param dir The directory node descriptor. + * @return A Map of names to node descriptors. */ - public void createFile(String path, String name); + public Map getDirectoryListing(AVMNodeDescriptor dir); /** - * Create a new Folder. + * Create a new File. Fails if the file already exists. + * @param path The simple absolute path to the parent. + * @param name The name to give the file. + * @param source A possibly null stream to draw initial contents from. + */ + public OutputStream createFile(String path, String name); + + /** + * Create a new directory. * @param parent The simple absolute path to the parent. * @param name The name to give the folder. */ @@ -69,26 +89,26 @@ public interface AVMService /** * Create a new layered file. - * @param srcPath The simple absolute path that the new file will shadow. + * @param srcPath The simple absolute path that the new file will point at. * @param parent The simple absolute path to the parent. * @param name The name to give the new file. */ public void createLayeredFile(String srcPath, String parent, String name); /** - * Create a new layered folder. - * @param srcPath The simple absolute path that the new folder will shadow. + * Create a new layered directory. + * @param srcPath The simple absolute path that the new folder will point at. * @param parent The simple absolute path to the parent. * @param name The name to give the new folder. */ public void createLayeredDirectory(String srcPath, String parent, String name); /** - * Retarget a layered folder. + * Retarget a layered directory. * @param path Path to the layered directory. * @param target The new target to aim at. */ - public void retargetLayeredFolder(String path, String target); + public void retargetLayeredDirectory(String path, String target); /** * Create a new Repository. All Repositories are top level in a hierarchical @@ -124,14 +144,13 @@ public interface AVMService public void rename(String srcParent, String srcName, String dstParent, String dstName); /** - * Slide a directory from one place in a layer to another uncovering what was - * underneath it. - * @param srcParent The simple absolute path to the parent folder. - * @param srcName The name of the node in the src Folder. - * @param dstParent The simple absolute path to the destination folder. - * @param dstName The name that the node will have in the destination folder. + * Uncover a name in a layered directory. That is, if the layered + * directory has a deleted entry of the given name remove that + * name from the deleted list. + * @param dirPath The path to the layered directory. + * @param name The name to uncover. */ - public void slide(String srcParent, String srcName, String dstParent, String dstName); + public void uncover(String dirPath, String name); /** * Get the latest version id of the repository. @@ -162,13 +181,35 @@ public interface AVMService */ public Set getRepositoryVersions(String name); + /** + * Get the names of all repositories. + * @return A List of all names. + */ + public List getRepositoryNames(); + + /** + * Get the specified root of a repository. + * @param version The version to look up. + * @param name The name of the repository. + * @return A descriptor for the specified root. + */ + public AVMNodeDescriptor getRepositoryRoot(int version, String name); + /** * Lookup a node by version ids and path. * @param version The version id to look under. * @param path The simple absolute path to the parent directory. - * @return A Lookup object. + * @return An AVMNodeDescriptor. */ - public Lookup lookup(int version, String path); + public AVMNodeDescriptor lookup(int version, String path); + + /** + * Lookup a node from a directory node. + * @param dir The descriptor for the directory node. + * @param name The name to lookup. + * @return The descriptor for the child. + */ + public AVMNodeDescriptor lookup(AVMNodeDescriptor dir, String name); /** * Get the indirection path for a layered file or directory. @@ -179,10 +220,10 @@ public interface AVMService public String getIndirectionPath(int version, String path); /** - * Destroy a repository. This is a complete wipe of a Repository. + * Purge a repository. This is a complete wipe of a Repository. * @param name The name of the Repository. */ - public void destroyRepository(String name); + public void purgeRepository(String name); /** * Purge a version from a repository. Deletes everything that lives in @@ -191,4 +232,10 @@ public interface AVMService * @param name The name of the Repository from which to purge it. */ public void purgeVersion(int version, String name); + + /** + * Make a directory into a primary indirection node. + * @param path The full path. + */ + public void makePrimary(String path); } diff --git a/source/java/org/alfresco/repo/avm/AVMServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java new file mode 100644 index 0000000000..c74cda73d7 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java @@ -0,0 +1,755 @@ +/* + * 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.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.repo.avm.SuperRepository; +import org.alfresco.repo.avm.hibernate.HibernateHelper; +import org.alfresco.repo.avm.hibernate.HibernateTxn; +import org.alfresco.repo.avm.hibernate.HibernateTxnCallback; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.tool.hbm2ddl.SchemaExport; + +/** + * Implements the AVMService. Stub. + * @author britt + */ +public class AVMServiceImpl implements AVMService +{ + /** + * The Hibernate SessionFactory. + */ + private SessionFactory fSessionFactory; + + /** + * The HibernateTxn. + */ + private HibernateTxn fTransaction; + + /** + * The SuperRepository for each service thread. + */ + private SuperRepository fSuperRepository; + + /** + * The storage directory. + */ + private String fStorage; + + /** + * The node id issuer. + */ + private Issuer fNodeIssuer; + + /** + * The content id issuer. + */ + private Issuer fContentIssuer; + + /** + * The layer id issuer. + */ + private Issuer fLayerIssuer; + + /** + * Basic constructor for the service. + * @param createTables Flag for whether tables should be created. + */ + public AVMServiceImpl() + { + fSessionFactory = HibernateHelper.GetSessionFactory(); + fTransaction = new HibernateTxn(fSessionFactory); + } + + /** + * Final initialization of the service. Must be called only on a + * fully initialized instance. + * @param createTables Whether we should create tables, and a default + * repository. + */ + public void init(boolean createTables) + { + if (createTables) + { + SchemaExport se = new SchemaExport(HibernateHelper.GetConfiguration()); + se.drop(false, true); + se.create(false, true); + File storage = new File(fStorage); + storage.mkdirs(); + fNodeIssuer = new Issuer(fStorage + File.separator + "node", 0L); + fContentIssuer = new Issuer(fStorage + File.separator + "content", 0L); + fLayerIssuer = new Issuer(fStorage + File.separator + "layer", 0L); + fSuperRepository = new SuperRepository(fNodeIssuer, + fContentIssuer, + fLayerIssuer, + fStorage); + createRepository("main"); + } + else + { + try + { + fNodeIssuer = new Issuer(fStorage + File.separator + "node"); + fContentIssuer = new Issuer(fStorage + File.separator + "content"); + fLayerIssuer = new Issuer(fStorage + File.separator + "layer"); + fSuperRepository = new SuperRepository(fNodeIssuer, + fContentIssuer, + fLayerIssuer, + fStorage); + } + catch (Exception e) + { + // TODO Log this and abort in some useful way. + } + } + } + + /** + * Set the location of file storage. + * @param storage + */ + public void setStorage(String storage) + { + fStorage = storage; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#getFileInputStream(int, java.lang.String) + */ + public InputStream getFileInputStream(final int version, final String path) + { + if (path == null) + { + throw new AVMBadArgumentException("Null path."); + } + class HTxnCallback implements HibernateTxnCallback + { + public InputStream in = null; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + in = fSuperRepository.getInputStream(version, path); + } + }; + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.in; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#getFileOutputStream(java.lang.String) + */ + public OutputStream getFileOutputStream(final String path) + { + if (path == null) + { + throw new AVMBadArgumentException("Null path."); + } + class HTxnCallback implements HibernateTxnCallback + { + public OutputStream out = null; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + out = fSuperRepository.getOutputStream(path); + } + }; + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + return doit.out; + } + + /** + * Get a random access file to the given file. + * @param version The version to look for (read-only) + * @param path The path to the file. + * @param access The access mode for RandomAccessFile + * @return A Random Access File. + */ + public RandomAccessFile getRandomAccess(final int version, final String path, final String access) + { + if (path == null || access == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public RandomAccessFile file; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + file = fSuperRepository.getRandomAccess(version, path, access); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + return doit.file; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#getFolderListing(int, java.lang.String) + */ + public List getDirectoryListing(final int version, final String path) + { + if (path == null) + { + throw new AVMBadArgumentException("Null path."); + } + class HTxnCallback implements HibernateTxnCallback + { + public List listing; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + listing = fSuperRepository.getListing(version, path); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.listing; + } + + /** + * Get a directory listing from a node descriptor. + * @param dir The directory node descriptor. + * @return A Map of names to node descriptors. + */ + public Map getDirectoryListing(final AVMNodeDescriptor dir) + { + if (dir == null) + { + throw new AVMBadArgumentException("Null descriptor."); + } + class HTxnCallback implements HibernateTxnCallback + { + public Map listing; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + listing = fSuperRepository.getListing(dir); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.listing; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createFile(java.lang.String, java.lang.String) + */ + public OutputStream createFile(final String path, final String name) + { + if (path == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public OutputStream out; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + out = fSuperRepository.createFile(path, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + return doit.out; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createFolder(java.lang.String, java.lang.String) + */ + public void createDirectory(final String path, final String name) + { + if (path == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.createDirectory(path, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createLayeredFile(java.lang.String, java.lang.String, java.lang.String) + */ + public void createLayeredFile(final String srcPath, final String parent, final String name) + { + if (srcPath == null || parent == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.createLayeredFile(srcPath, parent, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createLayeredFolder(java.lang.String, java.lang.String, java.lang.String) + */ + public void createLayeredDirectory(final String srcPath, final String parent, final String name) + { + if (srcPath == null || parent == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.createLayeredDirectory(srcPath, parent, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createRepository(java.lang.String) + */ + public void createRepository(final String name) + { + if (name == null) + { + throw new AVMBadArgumentException("Name is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.createRepository(name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createBranch(int, java.lang.String, java.lang.String, java.lang.String) + */ + public void createBranch(final int version, final String srcPath, final String dstPath, + final String name) + { + if (srcPath == null || dstPath == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.createBranch(version, srcPath, dstPath, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#removeNode(java.lang.String, java.lang.String) + */ + public void removeNode(final String parent, final String name) + { + if (parent == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.remove(parent, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#rename(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + public void rename(final String srcParent, final String srcName, final String dstParent, + final String dstName) + { + if (srcParent == null || srcName == null || dstParent == null || dstName == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.rename(srcParent, srcName, dstParent, dstName); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /** + * 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(final String dirPath, final String name) + { + if (dirPath == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.uncover(dirPath, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#getLatestVersionID(java.lang.String) + */ + public int getLatestVersionID(final String repName) + { + if (repName == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public int latestVersionID; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + latestVersionID = fSuperRepository.getLatestVersionID(repName); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.latestVersionID; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createSnapshot(java.util.List) + */ + public void createSnapshot(final List repositories) + { + if (repositories == null) + { + throw new AVMBadArgumentException("Repositories is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.createSnapshot(repositories); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#createSnapshot(java.lang.String) + */ + public void createSnapshot(final String repository) + { + if (repository == null) + { + throw new AVMBadArgumentException("Repository is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.createSnapshot(repository); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#lookup(int, java.lang.String) + */ + public AVMNodeDescriptor lookup(final int version, final String path) + { + if (path == null) + { + throw new AVMBadArgumentException("Path is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public AVMNodeDescriptor descriptor; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + Lookup lookup = fSuperRepository.lookup(version, path); + descriptor = lookup.getCurrentNode().getDescriptor(lookup); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.descriptor; + } + + /** + * Lookup a node descriptor from a directory node descriptor. + * @param dir The node descriptor of the directory. + * @param name The name to lookup. + * @return The node descriptor of the child. + */ + public AVMNodeDescriptor lookup(final AVMNodeDescriptor dir, final String name) + { + if (dir == null || name == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public AVMNodeDescriptor child; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + child = fSuperRepository.lookup(dir, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.child; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#destroyRepository(java.lang.String) + */ + public void purgeRepository(final String name) + { + if (name == null) + { + throw new AVMBadArgumentException("Name is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.purgeRepository(name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#purgeVersion(int, java.lang.String) + */ + public void purgeVersion(final int version, final String name) + { + if (name == null) + { + throw new AVMBadArgumentException("Name is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.purgeVersion(name, version); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#getIndirectionPath(java.lang.String) + */ + public String getIndirectionPath(final int version, final String path) + { + if (path == null) + { + throw new AVMBadArgumentException("Path is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public String indirectionPath; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + indirectionPath = fSuperRepository.getIndirectionPath(version, path); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.indirectionPath; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMService#getRepositoryVersions(java.lang.String) + */ + public Set getRepositoryVersions(final String name) + { + if (name == null) + { + throw new AVMBadArgumentException("Name is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public Set versions; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + versions = fSuperRepository.getRepositoryVersions(name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.versions; + } + + /** + * Change what a layered directory points to. + */ + public void retargetLayeredDirectory(final String path, final String target) + { + if (path == null || target == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.retargetLayeredDirectory(path, target); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + /** + * Make the indicated directory a primary indirection. + * @param path The absolute path. + */ + public void makePrimary(final String path) + { + if (path == null) + { + throw new AVMBadArgumentException("Path is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public void perform(Session session) + { + fSuperRepository.setSession(session); + fSuperRepository.makePrimary(path); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, true); + } + + public List getRepositoryNames() + { + class HTxnCallback implements HibernateTxnCallback + { + public List names; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + names = fSuperRepository.getRepositoryNames(); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.names; + } + + /** + * Get a descriptor for the specified repository root. + * @param version The version to get. + * @param name The name of the repository. + * @return The root descriptor. + */ + public AVMNodeDescriptor getRepositoryRoot(final int version, final String name) + { + if (name == null) + { + throw new AVMBadArgumentException("Name is null."); + } + class HTxnCallback implements HibernateTxnCallback + { + public AVMNodeDescriptor root; + + public void perform(Session session) + { + fSuperRepository.setSession(session); + root = fSuperRepository.getRepositoryRoot(version, name); + } + } + HTxnCallback doit = new HTxnCallback(); + fTransaction.perform(doit, false); + return doit.root; + } + } diff --git a/source/java/org/alfresco/repo/avm/AVMServicePerfTest.java b/source/java/org/alfresco/repo/avm/AVMServicePerfTest.java new file mode 100644 index 0000000000..888a5085b2 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMServicePerfTest.java @@ -0,0 +1,155 @@ +/* + * 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.PrintStream; + +/** + * Performance test(s). + * @author britt + */ +public class AVMServicePerfTest extends AVMServiceTestBase +{ + /** + * Test adding 100 files to each directory. + */ + public void testAdd100a() + { + try + { + String [] dirs = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" }; + for (String dir : dirs) + { + fService.createDirectory("main:/", dir); + String ndir = "main:/" + dir; + fService.createDirectory(ndir, dir); + ndir = ndir + "/" + dir; + for (int i = 0; i < 100; i++) + { + PrintStream out = new PrintStream(fService.createFile(ndir, "file" + i)); + out.println("I am " + ndir + "/file" + i); + System.out.println(ndir + "/file" + i); + out.close(); + } + fService.createSnapshot("main"); + } + // System.out.println(recursiveList("main", -1)); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test adding 100 files to each directory. + */ + public void testAdd100b() + { + try + { + String [] dirs = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" }; + for (String dir : dirs) + { + fService.createDirectory("main:/", dir); + String ndir = "main:/" + dir; + fService.createDirectory(ndir, dir); + ndir = ndir + "/" + dir; + for (int i = 0; i < 100; i++) + { + PrintStream out = new PrintStream(fService.createFile(ndir, "file" + i)); + out.println("I am " + ndir + "/file" + i); + System.out.println(ndir + "/file" + i); + out.close(); + } + fService.createSnapshot("main"); + } + // System.out.println(recursiveList("main", -1)); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test adding 100 files to each directory. + */ + public void testAdd100c() + { + try + { + String [] dirs = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" }; + for (String dir : dirs) + { + fService.createDirectory("main:/", dir); + String ndir = "main:/" + dir; + fService.createDirectory(ndir, dir); + ndir = ndir + "/" + dir; + for (int i = 0; i < 100; i++) + { + PrintStream out = new PrintStream(fService.createFile(ndir, "file" + i)); + out.println("I am " + ndir + "/file" + i); + System.out.println(ndir + "/file" + i); + out.close(); + } + fService.createSnapshot("main"); + } + // System.out.println(recursiveList("main", -1)); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test adding 100 files to each directory. + */ + public void testAdd100d() + { + try + { + String [] dirs = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" }; + for (String dir : dirs) + { + fService.createDirectory("main:/", dir); + String ndir = "main:/" + dir; + fService.createDirectory(ndir, dir); + ndir = ndir + "/" + dir; + for (int i = 0; i < 100; i++) + { + PrintStream out = new PrintStream(fService.createFile(ndir, "file" + i)); + out.println("I am " + ndir + "/file" + i); + out.close(); + System.out.println(ndir + "/file" + i); + } + fService.createSnapshot("main"); + } + // System.out.println(recursiveList("main", -1)); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTest.java b/source/java/org/alfresco/repo/avm/AVMServiceTest.java index 5040249347..950d872ac9 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceTest.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceTest.java @@ -20,66 +20,18 @@ package org.alfresco.repo.avm; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; +import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeMap; -import org.alfresco.repo.avm.hibernate.HibernateHelper; -import org.alfresco.repo.avm.impl.AVMServiceImpl; -import org.hibernate.cfg.Configuration; -import org.hibernate.stat.Statistics; -import org.hibernate.tool.hbm2ddl.SchemaExport; - -import junit.framework.TestCase; - /** * Big test of AVM behavior. * @author britt */ -public class AVMServiceTest extends TestCase +public class AVMServiceTest extends AVMServiceTestBase { - /** - * The AVMService we are testing. - */ - private AVMService fService; - - /** - * The start time of actual work for a test. - */ - private long fStartTime; - - /* (non-Javadoc) - * @see junit.framework.TestCase#setUp() - */ - @Override - protected void setUp() throws Exception - { - Configuration cfg = HibernateHelper.GetConfiguration(); -// HibernateHelper.GetSessionFactory().getStatistics().setStatisticsEnabled(true); - SchemaExport se = new SchemaExport(cfg); - se.drop(false, true); - AVMServiceImpl service = new AVMServiceImpl(); - service.setStorage("storage"); - service.init(true); - fStartTime = System.currentTimeMillis(); - fService = service; - } - - /* (non-Javadoc) - * @see junit.framework.TestCase#tearDown() - */ - @Override - protected void tearDown() throws Exception - { - long now = System.currentTimeMillis(); - System.out.println("Timing: " + (now - fStartTime) + "ms"); -// Statistics stats = HibernateHelper.GetSessionFactory().getStatistics(); -// stats.logSummary(); -// stats.clear(); - HibernateHelper.Reset(); - } - /** * Another test of renaming in a layer. */ @@ -92,8 +44,8 @@ public class AVMServiceTest extends TestCase fService.createDirectory("main:/", "a"); fService.createDirectory("main:/a", "b"); fService.createDirectory("main:/a", "c"); - fService.createFile("main:/a/b", "foo"); - fService.createFile("main:/a/c", "bar"); + fService.createFile("main:/a/b", "foo").close(); + fService.createFile("main:/a/c", "bar").close(); fService.createSnapshot("main"); // History is unchanged. checkHistory(history, "main"); @@ -103,7 +55,7 @@ public class AVMServiceTest extends TestCase // History is unchanged. checkHistory(history, "main"); // /a and /layer should have identical contents. - assertEquals(recursiveContents("main:/a", -1), recursiveContents("main:/layer", -1)); + assertEquals(recursiveContents("main:/a", -1, true), recursiveContents("main:/layer", -1, true)); // Now rename /layer/c/bar to /layer/b/bar fService.rename("main:/layer/c", "bar", "main:/layer/b", "bar"); fService.createSnapshot("main"); @@ -126,7 +78,7 @@ public class AVMServiceTest extends TestCase assertEquals(1, listing.size()); assertEquals("bar", listing.get(0).getName()); // Now make a file in /a/b - fService.createFile("main:/a/b", "baz"); + fService.createFile("main:/a/b", "baz").close(); fService.createSnapshot("main"); // History is unchanged. checkHistory(history, "main"); @@ -146,6 +98,7 @@ public class AVMServiceTest extends TestCase fService.createSnapshot("main"); // History is unchanged. checkHistory(history, "main"); + System.out.println(recursiveList("main", -1, true)); // /layer/b should have bar and foo. listing = fService.getDirectoryListing(-1, "main:/layer/b"); assertEquals(2, listing.size()); @@ -175,8 +128,8 @@ public class AVMServiceTest extends TestCase // Set up a handy hierarchy. fService.createDirectory("main:/", "a"); fService.createDirectory("main:/a", "b"); - fService.createFile("main:/a/b", "foo"); - fService.createFile("main:/a/b", "bar"); + fService.createFile("main:/a/b", "foo").close(); + fService.createFile("main:/a/b", "bar").close(); fService.createDirectory("main:/", "c"); fService.createDirectory("main:/c", "d"); fService.createSnapshot("main"); @@ -193,13 +146,13 @@ public class AVMServiceTest extends TestCase // History unchanged. checkHistory(history, "main"); // Now make a file in /layer/d - fService.createFile("main:/layer/d", "baz"); + fService.createFile("main:/layer/d", "baz").close(); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); // Make /a/d/figs and see the wackiness. fService.createDirectory("main:/a", "d"); - fService.createFile("main:/a/d", "figs"); + fService.createFile("main:/a/d", "figs").close(); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); @@ -221,9 +174,9 @@ public class AVMServiceTest extends TestCase } /** - * Test the slide operation. + * Test the uncover operation. */ - public void testSlide() + public void testUncover() { try { @@ -231,8 +184,8 @@ public class AVMServiceTest extends TestCase // Set up a handy hierarchy. fService.createDirectory("main:/", "a"); fService.createDirectory("main:/a/", "b"); - fService.createFile("main:/a/b", "foo"); - fService.createFile("main:/a/b", "bar"); + fService.createFile("main:/a/b", "foo").close(); + fService.createFile("main:/a/b", "bar").close(); fService.createDirectory("main:/", "c"); fService.createDirectory("main:/c", "d"); fService.createSnapshot("main"); @@ -249,13 +202,13 @@ public class AVMServiceTest extends TestCase // History unchanged. checkHistory(history, "main"); // Make a file in /layer/d - fService.createFile("main:/layer/d", "baz"); + fService.createFile("main:/layer/d", "baz").close(); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); // Make /a/d/figs and see the wackiness. fService.createDirectory("main:/a", "d"); - fService.createFile("main:/a/d", "figs"); + fService.createFile("main:/a/d", "figs").close(); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); @@ -264,8 +217,9 @@ public class AVMServiceTest extends TestCase assertEquals(2, listing.size()); assertEquals("baz", listing.get(0).getName()); assertEquals("figs", listing.get(1).getName()); - // Slide /layer/d to /layer/e - fService.slide("main:/layer", "d", "main:/layer", "e"); + // Rename /layer/d to /layer/e and uncover /layer/d + fService.rename("main:/layer", "d", "main:/layer", "e"); + fService.uncover("main:/layer", "d"); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); @@ -300,8 +254,8 @@ public class AVMServiceTest extends TestCase // Set up a handy hierarchy. fService.createDirectory("main:/", "a"); fService.createDirectory("main:/a", "b"); - fService.createFile("main:/a/b", "foo"); - fService.createFile("main:/a/b", "bar"); + fService.createFile("main:/a/b", "foo").close(); + fService.createFile("main:/a/b", "bar").close(); fService.createDirectory("main:/", "c"); fService.createDirectory("main:/c", "d"); fService.createSnapshot("main"); @@ -318,7 +272,7 @@ public class AVMServiceTest extends TestCase // History unchanged. checkHistory(history, "main"); // Add something to /a/b and it should show up in /b. - fService.createFile("main:/a/b", "baz"); + fService.createFile("main:/a/b", "baz").close(); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); @@ -329,7 +283,7 @@ public class AVMServiceTest extends TestCase assertEquals("baz", listing.get(1).getName()); assertEquals("foo", listing.get(2).getName()); // Add something to /a and it will show up in /layer. - fService.createFile("main:/a", "figs"); + fService.createFile("main:/a", "figs").close(); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); @@ -374,8 +328,8 @@ public class AVMServiceTest extends TestCase // Everything under /abranch should be identical in this version // and the previous. int version = fService.getLatestVersionID("main"); - assertEquals(recursiveContents("main:/abranch", version - 1), - recursiveContents("main:/abranch", version - 2)); + assertEquals(recursiveContents("main:/abranch", version - 1, true), + recursiveContents("main:/abranch", version - 2, true)); // Make a branch within a branch. fService.createBranch(-1, "main:/abranch/b/c", "main:/abranch/b", "cbranch"); fService.createSnapshot("main"); @@ -383,22 +337,22 @@ public class AVMServiceTest extends TestCase checkHistory(history, "main"); // Everything under /a should be unchanged between this version and the last. version = fService.getLatestVersionID("main"); - assertEquals(recursiveContents("main:/a", version - 1), - recursiveContents("main:/a", version - 2)); + assertEquals(recursiveContents("main:/a", version - 1, true), + recursiveContents("main:/a", version - 2, true)); // Make a branch to something outside of a branch inside a branch. fService.createBranch(-1, "main:/d", "main:/abranch", "dbranch"); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); // Make something ind /abranch/dbranch. - fService.createFile("main:/abranch/dbranch/e/f", "baz"); + fService.createFile("main:/abranch/dbranch/e/f", "baz").close(); fService.createSnapshot("main"); // History unchanged. checkHistory(history, "main"); // d should not have changed since the previous version. version = fService.getLatestVersionID("main"); - assertEquals(recursiveContents("main:/d", version - 1), - recursiveContents("main:/d", version - 2)); + assertEquals(recursiveContents("main:/d", version - 1, true), + recursiveContents("main:/d", version - 2, true)); for (String val : history.values()) { System.out.println(val); @@ -412,29 +366,105 @@ public class AVMServiceTest extends TestCase } /** - * Test adding 100 files to each directory. + * Test layers inside of layers. */ - public void testAdd100() + public void testLayersInLayers() { try { - String [] dirs = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" }; - for (String dir : dirs) - { - fService.createDirectory("main:/", dir); - dir = "main:/" + dir; - for (int i = 0; i < 50; i++) - { - fService.createFile(dir, "file" + i); - System.out.println(dir + "/file" + i); - PrintStream out = - new PrintStream(fService.getFileOutputStream(dir + "/file" + i)); - out.println("I am " + dir + "/file" + i); - out.close(); - } - } + TreeMap history = new TreeMap(); + setupBasicTree(); + // History unchanged. + checkHistory(history, "main"); + // Create a layer to /a + fService.createLayeredDirectory("main:/a", "main:/", "layer"); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); + // History unchanged. + checkHistory(history, "main"); + // Make a layer inside of a layer pointing to d. + fService.createLayeredDirectory("main:/d", "main:/layer", "under"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create a file in /layer/under/e/f. + fService.createFile("main:/layer/under/e/f", "baz").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create a file in /d/e. + fService.createFile("main:/d/e", "bow").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/under/e should contain bow and f. + List listing = fService.getDirectoryListing(-1, "main:/layer/under/e"); + assertEquals(2, listing.size()); + assertEquals("bow", listing.get(0).getName()); + assertEquals("f", listing.get(1).getName()); + // Put a new set of dirs in to be made into a layering under d. + fService.createDirectory("main:/", "g"); + fService.createDirectory("main:/g", "h"); + fService.createDirectory("main:/g/h", "i"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Make a layer in /d to /g. + fService.createLayeredDirectory("main:/g", "main:/d", "gover"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /d/gover should be identical to /layer/under/gover + assertEquals(recursiveContents("main:/d/gover", -1, true), + recursiveContents("main:/layer/under/gover", -1, true)); + // Create a file in /layer/under/gover/h/i + fService.createFile("main:/layer/under/gover/h/i", "moo").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /d should be unchanged before this version and the last + // and /g should be unchanged between this version and the last. + int version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/d", version - 1, true), + recursiveContents("main:/d", version - 2, true)); + assertEquals(recursiveContents("main:/g", version - 1, true), + recursiveContents("main:/g", version - 2, true)); + // Add a file through /d/gover/h/i + fService.createFile("main:/d/gover/h/i", "cow").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /g should not have changed since its last version. + version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/g", version - 1, true), + recursiveContents("main:/g", version - 2, true)); + // /layer/under/gover/h/i shows both moo and cow. + listing = fService.getDirectoryListing(-1, "main:/layer/under/gover/h/i"); + assertEquals(2, listing.size()); + assertEquals("cow", listing.get(0).getName()); + assertEquals("moo", listing.get(1).getName()); + // Rename /layer/under/gover to /layer/b/gover and see what happens. + fService.rename("main:/layer/under", "gover", "main:/layer/b", "gover"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // moo should be in /layer/b/gover/h/i + listing = fService.getDirectoryListing(-1, "main:/layer/b/gover/h/i"); + assertEquals(1, listing.size()); + assertEquals("moo", listing.get(0).getName()); + // Add a new file to /layer/b/gover/h/i + fService.createFile("main:/layer/b/gover/h/i", "oink").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/b/gover/h/i should contain moo, oink. + listing = fService.getDirectoryListing(-1, "main:/layer/b/gover/h/i"); + assertEquals(2, listing.size()); + assertEquals("moo", listing.get(0).getName()); + assertEquals("oink", listing.get(1).getName()); + for (String val : history.values()) + { + System.out.println(val); + } } catch (Exception e) { @@ -443,6 +473,157 @@ public class AVMServiceTest extends TestCase } } + /** + * Test behavior when one branches a layer. + */ + public void testLayerAndBranch() + { + try + { + TreeMap history = new TreeMap(); + // Create a basic tree. + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createFile("main:/a/b/c", "foo").close(); + fService.createFile("main:/a/b/c", "bar").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create a layer over /a + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /a and /layer should have identical contents. + assertEquals(recursiveContents("main:/a", -1, true), + recursiveContents("main:/layer", -1, true)); + // Make a modification in /layer + fService.createFile("main:/layer/b", "baz").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Branch off layer. + fService.createBranch(-1, "main:/layer", "main:/", "branch"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/b and /branch/b should have identical contents. + assertEquals(recursiveContents("main:/layer/b", -1, true), + recursiveContents("main:/branch/b", -1, true)); + // Create /branch/b/c/foo + fService.createFile("main:/branch/b/c", "baz").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer should not have changed. + int version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/layer", version - 1, true), + recursiveContents("main:/layer", version - 2, true)); + // Change something in /layer + fService.createFile("main:/layer/b/c", "fig").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /branch should not have changed. + version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/branch", version - 1, true), + recursiveContents("main:/branch", version - 2, true)); + // Create another layer on /a + fService.createLayeredDirectory("main:/a", "main:/", "layer2"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Branch from /layer2/b. + fService.createBranch(-1, "main:/layer2/b", "main:/", "branch2"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create something in the branch. + fService.createFile("main:/branch2", "goofy").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer2 should be unchanged. + version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/layer2", version - 1, true), + recursiveContents("main:/layer2", version - 2, true)); + // Remove something from /layer2 + fService.removeNode("main:/layer2/b/c", "foo"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /branch2 is unchanged. + version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/branch2", version - 1, true), + recursiveContents("main:/branch2", version - 2, true)); + // /a is unchanged. + assertEquals(recursiveContents("main:/a", version - 1, true), + recursiveContents("main:/a", version - 2, true)); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test scenario in which something is renamed from inside one independent layer to another. + */ + public void testRenameLayerToLayer() + { + try + { + TreeMap history = new TreeMap(); + // Set up two trees + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createFile("main:/a/b/c", "foo").close(); + fService.createFile("main:/a/b/c", "bar").close(); + fService.createDirectory("main:/", "d"); + fService.createDirectory("main:/d", "e"); + fService.createDirectory("main:/d/e", "f"); + fService.createFile("main:/d/e/f", "moo").close(); + fService.createFile("main:/d/e/f", "cow").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Make a layer to /a and a layer to /d + fService.createLayeredDirectory("main:/a", "main:/", "la"); + fService.createLayeredDirectory("main:/d", "main:/", "ld"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Move /la/b/c to /ld/e/c. + fService.rename("main:/la/b", "c", "main:/ld/e", "c"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create file /ld/e/c/baz. + fService.createFile("main:/ld/e/c", "baz").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Here's the thing we'd like to assert. + assertEquals("main:/a/b/c", fService.lookup(-1, "main:/ld/e/c").getIndirection()); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + /** * Test Nothing. Just make sure set up works. */ @@ -459,8 +640,7 @@ public class AVMServiceTest extends TestCase { fService.createDirectory("main:/", "testdir"); fService.createSnapshot("main"); - AVMNode node = fService.lookup(-1, "main:/").getCurrentNode(); - assertTrue(node instanceof PlainDirectoryNode); + assertEquals(AVMNodeType.PLAIN_DIRECTORY, fService.lookup(-1, "main:/").getType()); } catch (Exception e) { @@ -477,8 +657,8 @@ public class AVMServiceTest extends TestCase try { testCreateDirectory(); - fService.createFile("main:/testdir", "testfile"); - fService.createFile("main:/", "testfile2"); + fService.createFile("main:/testdir", "testfile").close(); + fService.createFile("main:/", "testfile2").close(); fService.createSnapshot("main"); PrintStream out = new PrintStream(fService.getFileOutputStream("main:/testdir/testfile")); out.println("This is testdir/testfile"); @@ -491,7 +671,7 @@ public class AVMServiceTest extends TestCase for (Integer version : versions) { System.out.println("V:" + version); - System.out.println(recursiveList("main", version)); + System.out.println(recursiveList("main", version, true)); } BufferedReader reader = new BufferedReader(new InputStreamReader(fService.getFileInputStream(-1, "main:/testdir/testfile"))); @@ -524,11 +704,11 @@ public class AVMServiceTest extends TestCase for (Integer version : versions) { System.out.println("V:" + version); - System.out.println(recursiveList("main", version)); + System.out.println(recursiveList("main", version, true)); } - String original = recursiveList("main:/a", -1, 0); + String original = recursiveList("main:/a", -1, 0, true); original = original.substring(original.indexOf('\n')); - String branch = recursiveList("main:/d/e/abranch", -1, 0); + String branch = recursiveList("main:/d/e/abranch", -1, 0, true); branch = branch.substring(branch.indexOf('\n')); assertEquals(original, branch); } @@ -549,13 +729,10 @@ public class AVMServiceTest extends TestCase setupBasicTree(); fService.createLayeredDirectory("main:/a", "main:/d/e", "alayer"); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); assertEquals("main:/a", fService.getIndirectionPath(-1, "main:/d/e/alayer")); - String original = recursiveList("main:/a", -1, 0); - original = original.substring(original.indexOf('\n')); - String layer = recursiveList("main:/d/e/alayer", -1, 0); - layer = original.substring(original.indexOf('\n')); - assertEquals(original, layer); + assertEquals(recursiveContents("main:/a", -1, true), + recursiveContents("main:/d/e/alayer", -1, true)); PrintStream out = new PrintStream(fService.getFileOutputStream("main:/d/e/alayer/b/c/foo")); out.println("I am main:/d/e/alayer/b/c/foo"); out.close(); @@ -565,7 +742,7 @@ public class AVMServiceTest extends TestCase String line = reader.readLine(); reader.close(); assertEquals("I am main:/a/b/c/foo", line); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); } catch (Exception e) { @@ -584,7 +761,7 @@ public class AVMServiceTest extends TestCase setupBasicTree(); fService.createLayeredFile("main:/a/b/c/foo", "main:/d", "lfoo"); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); assertEquals("main:/a/b/c/foo", fService.getIndirectionPath(-1, "main:/d/lfoo")); BufferedReader reader = new BufferedReader(new InputStreamReader(fService.getFileInputStream(-1, "main:/d/lfoo"))); @@ -595,7 +772,7 @@ public class AVMServiceTest extends TestCase out.println("I am main:/d/lfoo"); out.close(); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); reader = new BufferedReader(new InputStreamReader(fService.getFileInputStream(-1, "main:/a/b/c/foo"))); line = reader.readLine(); @@ -624,12 +801,9 @@ public class AVMServiceTest extends TestCase setupBasicTree(); fService.rename("main:/a", "b", "main:/d/e", "brenamed"); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); - String original = recursiveList("main:/a/b", 0, 0); - original = original.substring(original.indexOf('\n')); - String renamed = recursiveList("main:/d/e/brenamed", 1, 0); - renamed = renamed.substring(renamed.indexOf('\n')); - assertEquals(original, renamed); + System.out.println(recursiveList("main", -1, true)); + assertEquals(recursiveContents("main:/a/b", 1, true), + recursiveContents("main:/d/e/brenamed", 2, true)); } catch (Exception e) { @@ -678,14 +852,15 @@ public class AVMServiceTest extends TestCase { setupBasicTree(); fService.createRepository("second"); + List repoNames = fService.getRepositoryNames(); + assertEquals(2, repoNames.size()); + assertTrue(repoNames.contains("main")); + assertTrue(repoNames.contains("second")); fService.createBranch(-1, "main:/", "second:/", "main"); fService.createSnapshot("second"); - System.out.println(recursiveList("second", -1)); - String original = recursiveList("main:/", -1, 0); - original = original.substring(original.indexOf('\n')); - String branch = recursiveList("second:/main", -1, 0); - branch = branch.substring(branch.indexOf('\n')); - assertEquals(original, branch); + System.out.println(recursiveList("second", -1, true)); + assertEquals(recursiveContents("main:/", -1, true), + recursiveContents("second:/main", -1, true)); // Now make sure nothing happens to the branched from place, // if the branch is modified. PrintStream out = @@ -693,7 +868,7 @@ public class AVMServiceTest extends TestCase out.println("I am second:/main/a/b/c/foo"); out.close(); fService.createSnapshot("second"); - System.out.println(recursiveList("second", -1)); + System.out.println(recursiveList("second", -1, true)); BufferedReader reader = new BufferedReader(new InputStreamReader(fService.getFileInputStream(-1, "main:/a/b/c/foo"))); String line = reader.readLine(); @@ -718,19 +893,16 @@ public class AVMServiceTest extends TestCase fService.createRepository("second"); fService.createLayeredDirectory("main:/", "second:/", "main"); fService.createSnapshot("second"); - System.out.println(recursiveList("second", -1)); - String original = recursiveList("main:/", -1, 0); - original = original.substring(original.indexOf('\n')); - String layer = recursiveList("second:/main", -1, 0); - layer = layer.substring(layer.indexOf('\n')); - assertEquals(original, layer); + System.out.println(recursiveList("second", -1, true)); + assertEquals(recursiveContents("main:/", -1, true), + recursiveContents("second:/main", -1, true)); // Now make sure that a copy on write will occur and // that the underlying stuff doesn't get changed. PrintStream out = new PrintStream(fService.getFileOutputStream("second:/main/a/b/c/foo")); out.println("I am second:/main/a/b/c/foo"); out.close(); fService.createSnapshot("second"); - System.out.println(recursiveList("second", -1)); + System.out.println(recursiveList("second", -1, true)); BufferedReader reader = new BufferedReader(new InputStreamReader(fService.getFileInputStream(-1, "second:/main/a/b/c/foo"))); String line = reader.readLine(); @@ -741,6 +913,8 @@ public class AVMServiceTest extends TestCase line = reader.readLine(); reader.close(); assertEquals("I am main:/a/b/c/foo", line); + fService.purgeRepository("second"); + fService.purgeVersion(1, "main"); } catch (Exception e) { @@ -762,15 +936,12 @@ public class AVMServiceTest extends TestCase ArrayList toSnapshot = new ArrayList(); toSnapshot.add("main"); toSnapshot.add("second"); - System.out.println(recursiveList("main", -1)); - System.out.println(recursiveList("second", -1)); + System.out.println(recursiveList("main", -1, true)); + System.out.println(recursiveList("second", -1, true)); // Check that the moved thing has identical contents to the thing it // was moved from. - String original = recursiveList("main:/a/b/c", 0, 0); - original = original.substring(original.indexOf('\n')); - String moved = recursiveList("second:/cmoved", -1, 0); - moved = moved.substring(moved.indexOf('\n')); - assertEquals(original, moved); + assertEquals(recursiveContents("main:/a/b/c", 1, true), + recursiveContents("second:/cmoved", -1, true)); } catch (Exception e) { @@ -797,15 +968,15 @@ public class AVMServiceTest extends TestCase assertEquals("b", listing.get(0).getName()); fService.createLayeredDirectory("main:/a", "main:/", "c"); fService.createLayeredDirectory("main:/c", "main:/", "d"); - fService.createFile("main:/d/b", "foo.txt"); + fService.createFile("main:/d/b", "foo.txt").close(); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); listing = fService.getDirectoryListing(-1, "main:/d/b"); assertEquals(1, listing.size()); assertEquals("foo.txt", listing.get(0).getName()); - fService.createFile("main:/c/b", "bar.txt"); + fService.createFile("main:/c/b", "bar.txt").close(); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); listing = fService.getDirectoryListing(-1, "main:/c/b"); assertEquals(1, listing.size()); assertEquals("bar.txt", listing.get(0).getName()); @@ -815,7 +986,7 @@ public class AVMServiceTest extends TestCase assertEquals("foo.txt", listing.get(1).getName()); fService.rename("main:/", "c", "main:/", "e"); fService.createSnapshot("main"); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); listing = fService.getDirectoryListing(-1, "main:/d/b"); assertEquals(1, listing.size()); assertEquals("foo.txt", listing.get(0).getName()); @@ -837,55 +1008,41 @@ public class AVMServiceTest extends TestCase // Create a simple directory hierarchy. fService.createDirectory("main:/", "a"); fService.createDirectory("main:/a", "b"); - fService.createFile("main:/a/b", "c.txt"); - fService.createFile("main:/a/b", "d.txt"); - fService.createFile("main:/a", "e.txt"); + fService.createFile("main:/a/b", "c.txt").close(); + fService.createFile("main:/a/b", "d.txt").close(); + fService.createFile("main:/a", "e.txt").close(); fService.createSnapshot("main"); // Make a branch off of a. fService.createBranch(-1, "main:/a", "main:/", "branch"); fService.createSnapshot("main"); // The branch should contain exactly the same things as the thing // it branched from. - String original = recursiveList("main:/a", -1, 0); - original = original.substring(original.indexOf('\n')); - String branch = recursiveList("main:/branch", -1, 0); - branch = branch.substring(branch.indexOf('\n')); - assertEquals(original, branch); + assertEquals(recursiveContents("main:/a", -1, true), + recursiveContents("main:/branch", -1, true)); // Make a layer pointing to /branch/b fService.createLayeredDirectory("main:/branch/b", "main:/", "layer"); fService.createSnapshot("main"); // The new layer should contain exactly the same things as the thing it is layered to. - original = recursiveList("main:/branch/b", -1, 0); - original = original.substring(original.indexOf('\n')); - String layer = recursiveList("main:/layer", -1, 0); - layer = layer.substring(layer.indexOf('\n')); - assertEquals(original, layer); + assertEquals(recursiveContents("main:/branch/b", -1, true), + recursiveContents("main:/layer", -1, true)); // Make a modification in /a/b, the original branch. PrintStream out = new PrintStream(fService.getFileOutputStream("main:/a/b/c.txt")); out.println("I am c, modified in main:/a/b."); out.close(); fService.createSnapshot("main"); // The layer should still have identical content to /branch/b. - original = recursiveList("main:/branch/b", -1, 0); - original = original.substring(original.indexOf('\n')); - layer = recursiveList("main:/layer", -1, 0); - layer = layer.substring(layer.indexOf('\n')); - assertEquals(original, layer); + assertEquals(recursiveContents("main:/branch/b", -1, true), + recursiveContents("main:/layer", -1, true)); // But the layer won't have contents identical to /a/b's - original = recursiveList("main:/a/b", -1, 0); - original = original.substring(original.indexOf('\n')); - assertFalse(original.equals(layer)); + assertFalse(recursiveContents("main:/a/b", -1, true).equals(recursiveContents("main:/layer", -1, true))); // Make a modification in /branch/b out = new PrintStream(fService.getFileOutputStream("main:/branch/b/d.txt")); out.println("I am d, modified in main:/branch/b"); out.close(); fService.createSnapshot("main"); // The layer contents should be identical to the latest contents of /branch/b. - original = recursiveList("main:/branch/b", -1, 0); - original = original.substring(original.indexOf('\n')); - layer = recursiveList("main:/layer", -1, 0); - layer = layer.substring(layer.indexOf('\n')); - assertEquals(original, layer); + assertEquals(recursiveContents("main:/branch/b", -1, true), + recursiveContents("main:/layer", -1, true)); } catch (Exception e) { @@ -913,20 +1070,16 @@ public class AVMServiceTest extends TestCase fService.createLayeredDirectory("main:/f", "main:/", "g"); fService.createSnapshot("main"); // e, f, g should all have the same contents as a. - String a = recursiveList("main:/a", -1, 0); - a = a.substring(a.indexOf('\n')); - String e = recursiveList("main:/e", -1, 0); - e = e.substring(e.indexOf('\n')); - String f = recursiveList("main:/f", -1, 0); - f = f.substring(f.indexOf('\n')); - String g = recursiveList("main:/g", -1, 0); - g = g.substring(g.indexOf('\n')); + String a = recursiveContents("main:/a", -1, true); + String e = recursiveContents("main:/e", -1, true); + String f = recursiveContents("main:/f", -1, true); + String g = recursiveContents("main:/g", -1, true); assertEquals(a, e); assertEquals(a, f); assertEquals(a, g); // Now make a file in /g/b/c/d and /f/b/c/d - fService.createFile("main:/g/b/c/d", "foo"); - fService.createFile("main:/f/b/c/d", "bar"); + fService.createFile("main:/g/b/c/d", "foo").close(); + fService.createFile("main:/f/b/c/d", "bar").close(); fService.createSnapshot("main"); // /g/b/c/d should contain foo and bar. List listing = fService.getDirectoryListing(-1, "main:/g/b/c/d"); @@ -938,7 +1091,7 @@ public class AVMServiceTest extends TestCase assertEquals(1, listing.size()); assertEquals("bar", listing.get(0).getName()); // Now do something in the bottom layer. - fService.createFile("main:/a/b/c", "baz"); + fService.createFile("main:/a/b/c", "baz").close(); fService.createSnapshot("main"); // /e/b/c should contain baz and d listing = fService.getDirectoryListing(-1, "main:/e/b/c"); @@ -946,14 +1099,14 @@ public class AVMServiceTest extends TestCase assertEquals("baz", listing.get(0).getName()); assertEquals("d", listing.get(1).getName()); // Now add something in the e layer. - fService.createFile("main:/e/b/c/d", "bing"); + fService.createFile("main:/e/b/c/d", "bing").close(); fService.createSnapshot("main"); // /f/b/c/d should now contain bar and bing. listing = fService.getDirectoryListing(-1, "main:/f/b/c/d"); assertEquals(2, listing.size()); assertEquals("bar", listing.get(0).getName()); assertEquals("bing", listing.get(1).getName()); - System.out.println(recursiveList("main", -1)); + System.out.println(recursiveList("main", -1, true)); } catch (Exception e) { @@ -978,9 +1131,8 @@ public class AVMServiceTest extends TestCase fService.createLayeredDirectory("main:/a", "main:/", "layer"); fService.createSnapshot("main"); // /layer should have the same contents as /a at this point. - String original = recursiveContents("main:/a", -1); - String layer = recursiveContents("main:/layer", -1); - assertEquals(original, layer); + assertEquals(recursiveContents("main:/a", -1, true), + recursiveContents("main:/layer", -1, true)); // Now we will rename /layer/d to /layer/moved fService.rename("main:/layer", "d", "main:/layer", "moved"); fService.createSnapshot("main"); @@ -1005,95 +1157,645 @@ public class AVMServiceTest extends TestCase } } - /** - * Get the recursive contents of the given path and version. - * @param path - * @param version - * @return + * Test behavior of multiply layers not in register. */ - private String recursiveContents(String path, int version) + public void testMultiLayerUnregistered() { - String val = recursiveList(path, version, 0); - return val.substring(val.indexOf('\n')); - } - - - /** - * Helper to write a recursive listing of a repository at a given version. - * @param repoName The name of the repository. - * @param version The version to look under. - */ - private String recursiveList(String repoName, int version) - { - return recursiveList(repoName + ":/", version, 0); - } - - /** - * Recursive list the given path. - * @param path The path. - * @param version The version. - * @param indent The current indent level. - */ - private String recursiveList(String path, int version, int indent) - { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < indent; i++) + try { - builder.append(' '); - } - builder.append(path.substring(path.lastIndexOf('/') + 1)); - builder.append(' '); - Lookup lookup = fService.lookup(version, path); - AVMNode node = lookup.getCurrentNode(); - builder.append(node.toString(lookup)); - builder.append('\n'); - if (node instanceof DirectoryNode) - { - String basename = path.endsWith("/") ? path : path + "/"; - List listing = fService.getDirectoryListing(version, path); - for (FolderEntry entry : listing) + TreeMap history = new TreeMap(); + setupBasicTree(); + // History unchanged. + checkHistory(history, "main"); + // Create layered directory /d/e/f/ to /a + fService.createLayeredDirectory("main:/a", "main:/d/e/f", "l0"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create layered directory /d/l1 to /d/e/f. + fService.createLayeredDirectory("main:/d/e/f", "main:/d", "l1"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create layered directory /l2 to /d + fService.createLayeredDirectory("main:/d", "main:/", "l2"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create /l2/l1/l0/a/foo. + fService.createFile("main:/l2/l1/l0/b", "foo").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /l2/l1/l0 should now point at /d/l1/l0 + assertEquals("main:/d/l1/l0", fService.lookup(-1, "main:/l2/l1/l0").getIndirection()); + // /l2/l1/l0/b should now point at /d/l1/l0/b + assertEquals("main:/d/l1/l0/b", fService.lookup(-1, "main:/l2/l1/l0/b").getIndirection()); + for (String val : history.values()) { - builder.append(recursiveList(basename + entry.getName(), version, indent + 2)); + System.out.println(val); } } - return builder.toString(); - } - - /** - * Setup a basic tree. - */ - private void setupBasicTree() - { - fService.createDirectory("main:/", "a"); - fService.createDirectory("main:/a", "b"); - fService.createDirectory("main:/a/b", "c"); - fService.createDirectory("main:/", "d"); - fService.createDirectory("main:/d", "e"); - fService.createDirectory("main:/d/e", "f"); - fService.createFile("main:/a/b/c", "foo"); - PrintStream out = new PrintStream(fService.getFileOutputStream("main:/a/b/c/foo")); - out.println("I am main:/a/b/c/foo"); - out.close(); - fService.createFile("main:/a/b/c", "bar"); - out = new PrintStream(fService.getFileOutputStream("main:/a/b/c/bar")); - out.println("I am main:/a/b/c/bar"); - out.close(); - ArrayList toSnapshot = new ArrayList(); - toSnapshot.add("main"); - fService.createSnapshot(toSnapshot); - } - - /** - * Check that history has not been screwed up. - */ - private void checkHistory(TreeMap history, String repName) - { - for (Integer i : history.keySet()) + catch (Exception e) { - assertEquals(history.get(i), recursiveList(repName, i)); + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test makePrimary. + */ + public void testMakePrimary() + { + try + { + TreeMap history = new TreeMap(); + setupBasicTree(); + // History unchanged. + checkHistory(history, "main"); + // Make a layer to /a + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Make /layer/b/c primary. + fService.makePrimary("main:/layer/b/c"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Rename /layer/b/c to /layer/c + fService.rename("main:/layer/b", "c", "main:/layer", "c"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /a/b/c should have identical contents to /layer/c + assertEquals(recursiveContents("main:/a/b/c", -1, true), + recursiveContents("main:/layer/c", -1, true)); + // Create /layer2 to /a. + fService.createLayeredDirectory("main:/a", "main:/", "layer2"); + // Make a file down in /layer2/b/c + fService.createFile("main:/layer2/b/c", "baz").close(); + // make /layer2/b/c primary. + fService.makePrimary("main:/layer2/b/c"); + // Rename /layer2/b/c to /layer2/c + fService.rename("main:/layer2/b", "c", "main:/layer2", "c"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer2/c should contain foo bar and baz. + List listing = fService.getDirectoryListing(-1, "main:/layer2/c"); + assertEquals(3, listing.size()); + assertEquals("bar", listing.get(0).getName()); + assertEquals("baz", listing.get(1).getName()); + assertEquals("foo", listing.get(2).getName()); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test retargeting a directory. + */ + public void testRetarget() + { + try + { + TreeMap history = new TreeMap(); + setupBasicTree(); + // History unchanged. + checkHistory(history, "main"); + // Make a layer to /a + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Retarget /layer/b/c to /d. + fService.retargetLayeredDirectory("main:/layer/b/c", "main:/d"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/b/c should contain e. + List listing = fService.getDirectoryListing(-1, "main:/layer/b/c"); + assertEquals(1, listing.size()); + assertEquals("e", listing.get(0).getName()); + // Rename /layer/b/c to /layer/c + fService.rename("main:/layer/b", "c", "main:/layer", "c"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /d should have identical contents to /layer/c + assertEquals(recursiveContents("main:/d", -1, true), + recursiveContents("main:/layer/c", -1, true)); + // Create /layer2 to /a. + fService.createLayeredDirectory("main:/a", "main:/", "layer2"); + // Make a file down in /layer2/b/c + fService.createFile("main:/layer2/b/c", "baz").close(); + // make /layer2/b/c primary. + fService.retargetLayeredDirectory("main:/layer2/b/c", "main:/d"); + // Rename /layer2/b/c to /layer2/c + fService.rename("main:/layer2/b", "c", "main:/layer2", "c"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer2/c should have baz and e in it. + listing = fService.getDirectoryListing(-1, "main:/layer2/c"); + assertEquals(2, listing.size()); + assertEquals("baz", listing.get(0).getName()); + assertEquals("e", listing.get(1).getName()); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test rename between branches. + */ + public void testRenameBranchToBranch() + { + try + { + TreeMap history = new TreeMap(); + // Set up two trees + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createFile("main:/a/b/c", "foo").close(); + fService.createFile("main:/a/b/c", "bar").close(); + fService.createDirectory("main:/", "d"); + fService.createDirectory("main:/d", "e"); + fService.createDirectory("main:/d/e", "f"); + fService.createFile("main:/d/e/f", "moo").close(); + fService.createFile("main:/d/e/f", "cow").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Make branches. + fService.createBranch(-1, "main:/a/b", "main:/", "abranch"); + fService.createBranch(-1, "main:/d/e", "main:/", "dbranch"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Move /abranch/c/foo /dbranch/foo + fService.rename("main:/abranch/c", "foo", "main:/dbranch", "foo"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Confirm that /a and /d are unchanged. + int version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/a", version - 1, true), + recursiveContents("main:/a", version - 2, true)); + assertEquals(recursiveContents("main:/d", version - 1, true), + recursiveContents("main:/d", version - 2, true)); + // Move /dbranch/f to /abranch/c/f + fService.rename("main:/dbranch", "f", "main:/abranch/c", "f"); + fService.createSnapshot("main"); + // Confirm that /a and /d are unchanged. + version = fService.getLatestVersionID("main"); + assertEquals(recursiveContents("main:/a", version - 1, true), + recursiveContents("main:/a", version - 2, true)); + assertEquals(recursiveContents("main:/d", version - 1, true), + recursiveContents("main:/d", version - 2, true)); + // History unchanged. + checkHistory(history, "main"); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test a branch being created in a layer. + */ + public void testBranchIntoLayer() + { + try + { + TreeMap history = new TreeMap(); + // Set up two trees + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createFile("main:/a/b/c", "foo").close(); + fService.createFile("main:/a/b/c", "bar").close(); + fService.createDirectory("main:/", "d"); + fService.createDirectory("main:/d", "e"); + fService.createDirectory("main:/d/e", "f"); + fService.createFile("main:/d/e/f", "moo").close(); + fService.createFile("main:/d/e/f", "cow").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create a layer to /a + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Now create a branch from /d in /layer/a/b. + fService.createBranch(-1, "main:/d", "main:/layer/b", "branch"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Now modify /layer/b/branch/e/f/moo. + PrintStream out = new PrintStream(fService.getFileOutputStream("main:/layer/b/branch/e/f/moo")); + out.println("moo modified."); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/b/branch/e/f should contain moo and cow. + List listing = fService.getDirectoryListing(-1, "main:/layer/b/branch/e/f"); + assertEquals(2, listing.size()); + assertEquals("cow", listing.get(0).getName()); + assertEquals("moo", listing.get(1).getName()); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test renaming into a layer. + */ + public void testRenameIntoLayer() + { + try + { + TreeMap history = new TreeMap(); + // Set up two trees + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createFile("main:/a/b/c", "foo").close(); + fService.createFile("main:/a/b/c", "bar").close(); + fService.createDirectory("main:/", "d"); + fService.createDirectory("main:/d", "e"); + fService.createDirectory("main:/d/e", "f"); + fService.createFile("main:/d/e/f", "moo").close(); + fService.createFile("main:/d/e/f", "cow").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Create a layer to /a + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Now rename /d into /layer/a/b. + fService.rename("main:/", "d", "main:/layer/b", "d"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Now modify /layer/b/branch/e/f/moo. + PrintStream out = new PrintStream(fService.getFileOutputStream("main:/layer/b/d/e/f/moo")); + out.println("moo modified."); + out.close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/b/branch/e/f should contain moo and cow. + List listing = fService.getDirectoryListing(-1, "main:/layer/b/d/e/f"); + assertEquals(2, listing.size()); + assertEquals("cow", listing.get(0).getName()); + assertEquals("moo", listing.get(1).getName()); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test proper indirection behavior. + */ + public void testIndirectionBehavior() + { + try + { + TreeMap history = new TreeMap(); + // Setup the stage. + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createDirectory("main:/a/b/c", "d"); + fService.createDirectory("main:/a/b/c/d", "e"); + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + fService.createDirectory("main:/", "f"); + fService.createDirectory("main:/f", "g"); + fService.createDirectory("main:/f/g", "h"); + fService.createLayeredDirectory("main:/f", "main:/", "flayer"); + fService.createLayeredDirectory("main:/flayer", "main:/layer/b/c", "fover"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + fService.createDirectory("main:/", "i"); + fService.createDirectory("main:/i", "j"); + fService.createDirectory("main:/i/j", "k"); + fService.createLayeredDirectory("main:/i", "main:/f/g/h", "iover"); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + fService.createFile("main:/layer/b/c/fover/g/h/iover/j/k", "foo").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // Make a file in /i/j/k + fService.createFile("main:/i/j/k", "pismo").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/b/c/fover/g/h/iover/j/k should contain pismo and foo. + List listing = fService.getDirectoryListing(-1, "main:/layer/b/c/fover/g/h/iover/j/k"); + assertEquals(2, listing.size()); + assertEquals("foo", listing.get(0).getName()); + assertEquals("pismo", listing.get(1).getName()); + // Make a file in /flayer/g/h/iover/j/k + fService.createFile("main:/flayer/g/h/iover/j/k", "zuma").close(); + fService.createSnapshot("main"); + // History unchanged. + checkHistory(history, "main"); + // /layer/b/c/fover/g/h/iover/j/k should contain foo, pismo, and zuma. + listing = fService.getDirectoryListing(-1, "main:/layer/b/c/fover/g/h/iover/j/k"); + assertEquals(3, listing.size()); + assertEquals("foo", listing.get(0).getName()); + assertEquals("pismo", listing.get(1).getName()); + assertEquals("zuma", listing.get(2).getName()); + for (String val : history.values()) + { + System.out.println(val); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test reading of versioned content via a layer. + */ + public void testVersionedRead() + { + try + { + PrintStream out = new PrintStream(fService.createFile("main:/", "foo")); + out.print("version1"); + out.close(); + fService.createLayeredFile("main:/foo", "main:/", "afoo"); + fService.createSnapshot("main"); + assertEquals(8, fService.lookup(-1, "main:/foo").getLength()); + out = new PrintStream(fService.getFileOutputStream("main:/foo")); + out.print("version2"); + out.close(); + fService.createSnapshot("main"); + BufferedReader reader = + new BufferedReader(new InputStreamReader(fService.getFileInputStream(1, "main:/afoo"))); + assertEquals("version2", reader.readLine()); + reader.close(); + reader = + new BufferedReader(new InputStreamReader(fService.getFileInputStream(2, "main:/afoo"))); + assertEquals("version2", reader.readLine()); + reader.close(); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test rename of an overlayed directory contained in an overlayed + * directory. + */ + public void testRenameLayerInLayer() + { + try + { + // Make some directories. + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createSnapshot("main"); + // Make a layer to /a. + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.createSnapshot("main"); + // Force a copy on write in the layer. + fService.createFile("main:/layer/b/c", "foo").close(); + fService.createSnapshot("main"); + assertEquals("main:/a/b/c", fService.lookup(-1, "main:/layer/b/c").getIndirection()); + // Now rename. + fService.rename("main:/layer/b", "c", "main:/layer/b", "d"); + fService.createSnapshot("main"); + assertEquals("main:/a/b/d", fService.lookup(-1, "main:/layer/b/d").getIndirection()); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Yet another rename from layer to layer test. + */ + public void testAnotherRename() + { + try + { + // Make two directory trees. + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createDirectory("main:/", "d"); + fService.createDirectory("main:/d", "e"); + fService.createDirectory("main:/d/e", "f"); + fService.createSnapshot("main"); + // Make a layer over each. + fService.createLayeredDirectory("main:/a", "main:/", "la"); + fService.createLayeredDirectory("main:/d", "main:/", "ld"); + fService.createSnapshot("main"); + // rename from down in one layer to another. + fService.rename("main:/ld/e", "f", "main:/la/b", "f"); + fService.createSnapshot("main"); + AVMNodeDescriptor desc = fService.lookup(-1, "main:/la/b/f"); + assertTrue(desc.isPrimary()); + assertEquals("main:/d/e/f", desc.getIndirection()); + // Now rename in in the layer. + fService.rename("main:/la/b", "f", "main:/la/b/c", "f"); + fService.createSnapshot("main"); + desc = fService.lookup(-1, "main:/la/b/c/f"); + assertTrue(desc.isPrimary()); + assertEquals("main:/d/e/f", desc.getIndirection()); + // Now create a directory in the layered f. + fService.createDirectory("main:/la/b/c/f", "dir"); + fService.createSnapshot("main"); + desc = fService.lookup(-1, "main:/la/b/c/f/dir"); + assertFalse(desc.isPrimary()); + assertEquals("main:/d/e/f/dir", desc.getIndirection()); + // Now rename that and see where it points. + fService.rename("main:/la/b/c/f", "dir", "main:/la/b", "dir"); + fService.createSnapshot("main"); + desc = fService.lookup(-1, "main:/la/b/dir"); + assertFalse(desc.isPrimary()); + assertEquals("main:/a/b/dir", desc.getIndirection()); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test rename behavior of an overlayed file withing a layer. + */ + public void testFileRenameLayer() + { + try + { + // Make a set of directories. + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createFile("main:/", "foo").close(); + fService.createSnapshot("main"); + // Make a layer. + fService.createLayeredDirectory("main:/a", "main:/", "la"); + fService.createSnapshot("main"); + // Make a layered file. + fService.createLayeredFile("main:/foo", "main:/la/b", "foo"); + fService.createSnapshot("main"); + AVMNodeDescriptor desc = fService.lookup(-1, "main:/la/b/foo"); + assertEquals("main:/foo", desc.getIndirection()); + // Now rename it. It should still point at the same place. + fService.rename("main:/la/b", "foo", "main:/la", "foo"); + fService.createSnapshot("main"); + desc = fService.lookup(-1, "main:/la/foo"); + assertEquals("main:/foo", desc.getIndirection()); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * The random access. + */ + public void testRandomAccess() + { + try + { + setupBasicTree(); + RandomAccessFile file = fService.getRandomAccess(1, "main:/a/b/c/foo", "r"); + byte [] buff = new byte[256]; + assertTrue(file.read(buff) >= 20); + file.close(); + file = fService.getRandomAccess(-1, "main:/a/b/c/bar", "rw"); + for (int i = 0; i < 256; i++) + { + buff[i] = (byte)i; + } + file.write(buff); + file.close(); + fService.createSnapshot("main"); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test COW during long operations. + */ + public void testCOWLongOps() + { + try + { + setupBasicTree(); + // Create a layer to a. + fService.createLayeredDirectory("main:/a", "main:/d/e/f", "layer"); + // Create a layer to /d + fService.createLayeredDirectory("main:/d", "main:/", "l2"); + // Force a copy on write on l2 + fService.createFile("main:/l2", "baz").close(); + // Force a copy on write on /d/e/f/layer/b/c + fService.createFile("main:/d/e/f/layer/b/c", "fink").close(); + // Create /l2/e/f/layer/b/c/nottle + fService.createFile("main:/l2/e/f/layer/b/c", "nottle").close(); + fService.createSnapshot("main"); + System.out.println(recursiveList("main", -1, true)); + assertFalse(fService.lookup(-1, "main:/d/e/f/layer/b/c").getId() == + fService.lookup(-1, "main:/l2/e/f/layer/b/c").getId()); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + + /** + * Test new lookup methods. + */ + public void testLookup() + { + try + { + setupBasicTree(); + AVMNodeDescriptor desc = fService.getRepositoryRoot(-1, "main"); + assertNotNull(desc); + System.out.println(desc.toString()); + AVMNodeDescriptor child = fService.lookup(desc, "a"); + assertNotNull(child); + System.out.println(child.toString()); + child = fService.lookup(child, "b"); + assertNotNull(child); + System.out.println(child.toString()); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); } - int latest = fService.getLatestVersionID(repName); - history.put(latest - 1, recursiveList(repName, -1)); } } diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java b/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java new file mode 100644 index 0000000000..8085c2b9a7 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java @@ -0,0 +1,168 @@ +/* + * 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.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import org.alfresco.repo.avm.hibernate.HibernateHelper; +import org.hibernate.stat.Statistics; + +import junit.framework.TestCase; + +/** + * Base class for AVMService tests. + * @author britt + */ +public class AVMServiceTestBase extends TestCase +{ + /** + * The AVMService we are testing. + */ + protected AVMService fService; + + /** + * The start time of actual work for a test. + */ + private long fStartTime; + + /* (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception + { + HibernateHelper.GetSessionFactory().getStatistics().setStatisticsEnabled(true); + AVMServiceImpl service = new AVMServiceImpl(); + service.setStorage("build/test-results/storage"); + service.init(true); + fStartTime = System.currentTimeMillis(); + fService = service; + } + + /* (non-Javadoc) + * @see junit.framework.TestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception + { + long now = System.currentTimeMillis(); + System.out.println("Timing: " + (now - fStartTime) + "ms"); + Statistics stats = HibernateHelper.GetSessionFactory().getStatistics(); + stats.logSummary(); + stats.clear(); + HibernateHelper.Reset(); + } + + /** + * Get the recursive contents of the given path and version. + * @param path + * @param version + * @return + */ + protected String recursiveContents(String path, int version, boolean followLinks) + { + String val = recursiveList(path, version, 0, followLinks); + return val.substring(val.indexOf('\n')); + } + + + /** + * Helper to write a recursive listing of a repository at a given version. + * @param repoName The name of the repository. + * @param version The version to look under. + */ + protected String recursiveList(String repoName, int version, boolean followLinks) + { + return recursiveList(repoName + ":/", version, 0, followLinks); + } + + /** + * Recursive list the given path. + * @param path The path. + * @param version The version. + * @param indent The current indent level. + */ + protected String recursiveList(String path, int version, int indent, boolean followLinks) + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < indent; i++) + { + builder.append(' '); + } + builder.append(path.substring(path.lastIndexOf('/') + 1)); + builder.append(' '); + AVMNodeDescriptor desc = fService.lookup(version, path); + builder.append(desc.toString()); + builder.append('\n'); + if (desc.getType() == AVMNodeType.PLAIN_DIRECTORY || + (desc.getType() == AVMNodeType.LAYERED_DIRECTORY && followLinks)) + { + String basename = path.endsWith("/") ? path : path + "/"; + List listing = fService.getDirectoryListing(version, path); + for (FolderEntry entry : listing) + { + builder.append(recursiveList(basename + entry.getName(), version, indent + 2, followLinks)); + } + } + return builder.toString(); + } + + /** + * Setup a basic tree. + */ + protected void setupBasicTree() + throws IOException + { + fService.createDirectory("main:/", "a"); + fService.createDirectory("main:/a", "b"); + fService.createDirectory("main:/a/b", "c"); + fService.createDirectory("main:/", "d"); + fService.createDirectory("main:/d", "e"); + fService.createDirectory("main:/d/e", "f"); + fService.createFile("main:/a/b/c", "foo").close(); + PrintStream out = new PrintStream(fService.getFileOutputStream("main:/a/b/c/foo")); + out.println("I am main:/a/b/c/foo"); + out.flush(); + out.close(); + fService.createFile("main:/a/b/c", "bar").close(); + out = new PrintStream(fService.getFileOutputStream("main:/a/b/c/bar")); + out.println("I am main:/a/b/c/bar"); + out.flush(); + out.close(); + ArrayList toSnapshot = new ArrayList(); + toSnapshot.add("main"); + fService.createSnapshot(toSnapshot); + } + + /** + * Check that history has not been screwed up. + */ + protected void checkHistory(TreeMap history, String repName) + { + for (Integer i : history.keySet()) + { + assertEquals(history.get(i), recursiveList(repName, i, false)); + } + int latest = fService.getLatestVersionID(repName); + history.put(latest - 1, recursiveList(repName, -1, false)); + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMStressTest.java b/source/java/org/alfresco/repo/avm/AVMStressTest.java new file mode 100644 index 0000000000..0251dbfa96 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMStressTest.java @@ -0,0 +1,106 @@ +/* + * 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.util.ArrayList; +import java.util.List; + +import org.alfresco.repo.avm.util.BulkLoad; + +/** + * This is a stress test for the AVM repository. + * @author britt + */ +public class AVMStressTest extends AVMServiceTestBase +{ + /** + * Test N threads + */ + public void testNThreads() + { + try + { + BulkLoad loader = new BulkLoad(fService); + loader.recursiveLoad("source", "main:/"); + List testers = new ArrayList(); + List threads = new ArrayList(); + for (int i = 0; i < 1; i++) + { + AVMTester tester + = new AVMTester(400, // create file. + 10, // create dir, + 0, // rename + 2, // create layered dir + 5, // create layered file + 10, // remove node + 20, // modify file. + 3600, // read file + 10, // snapshot + 80000, // # ops + fService, + "" + i); + tester.Refresh(); + Thread thread = new Thread(tester); + testers.add(tester); + threads.add(thread); + } + for (Thread thread : threads) + { + thread.start(); + } + int exited = 0; + while (exited != 1) + { + try + { + Thread.sleep(2000); + for (int i = 0; i < 1; i++) + { + if (threads.get(i) == null) + { + continue; + } + threads.get(i).join(1000); + if (!threads.get(i).isAlive()) + { + threads.set(i, null); + if (testers.get(i).getError()) + { + for (AVMTester tester : testers) + { + tester.setExit(); + } + fail(); + } + exited++; + } + } + } + catch (InterruptedException e) + { + // Do nothing. + } + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMTester.java b/source/java/org/alfresco/repo/avm/AVMTester.java new file mode 100644 index 0000000000..57037cc383 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMTester.java @@ -0,0 +1,610 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +/** + * This is a Runnable which randomly performs operations on an AVM Repository. + * It's purpose is to act as a single thread in a multithreaded stress tester. + * @author britt + */ +class AVMTester implements Runnable +{ + // Operation codes. + private static final int CREATE_FILE = 0; + private static final int CREATE_DIR = 1; + private static final int RENAME = 2; + private static final int CREATE_LAYERED_DIR = 3; + private static final int CREATE_LAYERED_FILE = 4; + private static final int REMOVE_NODE = 5; + private static final int MODIFY_FILE = 6; + private static final int READ_FILE = 7; + private static final int SNAPSHOT = 8; + + private static List fgAllPaths; + private static List fgAllDirectories; + private static List fgAllFiles; + + private static boolean fgFrozen = false; + + /** + * The operation table. + */ + private int [] fOpTable; + + /** + * The number of operations to perform. + */ + private int fOpCount; + + /** + * The AVMService instance. + */ + private AVMService fService; + + /** + * The random number generators. + */ + private static Random fgRandom = new Random(); + + /** + * Names for nodes. + */ + private String[] fNames; + + /** + * The id of this thread. + */ + private String fID; + + /** + * Flag for whether this thread errored out. + */ + private boolean fError; + + /** + * Flag for whether this thread should exit. + */ + private boolean fExit; + + /** + * Initialize this with the relative frequencies of differents operations. + * @param createFile + * @param createDir + * @param rename + * @param createLayeredDir + * @param createLayeredFile + * @param removeNode + * @param modifyFile + * @param readFile + * @param snapshot + * @param opCount The number of operations to perform. + * @param service The instance of AVMService. + */ + public AVMTester(int createFile, + int createDir, + int rename, + int createLayeredDir, + int createLayeredFile, + int removeNode, + int modifyFile, + int readFile, + int snapshot, + int opCount, + AVMService service, + String id) + { + fError = false; + fExit = false; + fID = id; + fService = service; + fOpCount = opCount; + int count = createFile + createDir + rename + createLayeredDir + + createLayeredFile + removeNode + modifyFile + readFile + + snapshot; + fOpTable = new int[count]; + int off = 0; + for (int i = 0; i < createFile; i++) + { + fOpTable[off + i] = CREATE_FILE; + } + off += createFile; + for (int i = 0; i < createDir; i++) + { + fOpTable[off + i] = CREATE_DIR; + } + off += createDir; + for (int i = 0; i < rename; i++) + { + fOpTable[off + i] = RENAME; + } + off += rename; + for (int i = 0; i < createLayeredDir; i++) + { + fOpTable[off + i] = CREATE_LAYERED_DIR; + } + off += createLayeredDir; + for (int i = 0; i < createLayeredFile; i++) + { + fOpTable[off + i] = CREATE_LAYERED_FILE; + } + off += createLayeredFile; + for (int i = 0; i < removeNode; i++) + { + fOpTable[off + i] = REMOVE_NODE; + } + off += removeNode; + for (int i = 0; i < modifyFile; i++) + { + fOpTable[off + i] = MODIFY_FILE; + } + off += modifyFile; + for (int i = 0; i < readFile; i++) + { + fOpTable[off + i] = READ_FILE; + } + off += readFile; + for (int i = 0; i < snapshot; i++) + { + fOpTable[off + i] = SNAPSHOT; + } + off += snapshot; + // Generate a bunch of names. + String [] letters = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", + "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", + "u", "v", "w", "x", "y", "z" }; + fNames = new String[26 * 26]; + for (int i = 0; i < 26; i++) + { + for (int j = 0; j < 26; j++) + { + fNames[i * 26 + j] = letters[i] + letters[j]; + } + } + } + + /** + * It's off. + */ + public void run() + { + try + { + long startTime = System.currentTimeMillis(); + for (int i = 0; i < fOpCount; i++) + { + if (fgFrozen) + { + Thread.sleep(3600000); + } + if (fExit) + { + return; + } + System.out.print(fID + ":" + i + ":"); + int which = fgRandom.nextInt(fOpTable.length); + switch (fOpTable[which]) + { + case CREATE_FILE : + createFile(); + break; + case CREATE_DIR : + createDirectory(); + break; + case RENAME : + rename(); + break; + case CREATE_LAYERED_DIR : + createLayeredDir(); + break; + case CREATE_LAYERED_FILE : + createLayeredFile(); + break; + case REMOVE_NODE : + removeNode(); + break; + case MODIFY_FILE : + modifyFile(); + break; + case READ_FILE : + readFile(); + break; + case SNAPSHOT : + snapshot(); + break; + } + } + System.out.println(fgAllPaths.size() + " fses in " + (System.currentTimeMillis() - startTime) + + "ms"); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fgFrozen = true; + fError = true; + } + } + + private void createFile() + { + String name = "PF" + fNames[fgRandom.nextInt(26 * 26)]; + String path = RandomDirectory(); + try + { + System.out.println("create " + path + " " + name); + PrintStream out = new PrintStream(fService.createFile(path, name)); + out.println(path + "/" + name); + out.close(); + AddFile(appendPath(path, name)); + } + catch (AVMException ae) + { + if (ae instanceof AVMExistsException || + ae instanceof AVMNotFoundException || + ae instanceof AVMWrongTypeException || + ae instanceof AVMCycleException) + { + return; + } + throw ae; + } + } + + private void createDirectory() + { + String name = "PD" + fNames[fgRandom.nextInt(26 * 26)]; + String path = RandomDirectory(); + try + { + System.out.println("mkdir " + path + " " + name); + fService.createDirectory(path, name); + AddDirectory(appendPath(path, name)); + } + catch (AVMException ae) + { + if (ae instanceof AVMExistsException || + ae instanceof AVMNotFoundException || + ae instanceof AVMWrongTypeException || + ae instanceof AVMCycleException) + { + return; + } + throw ae; + } + } + + private void rename() + { + String name = fNames[fgRandom.nextInt(26 * 26)]; + String path = RandomPath(); + AVMNodeDescriptor desc = fService.lookup(-1, path); + if (path.equals("main:/")) + { + return; + } + int lastSlash = path.lastIndexOf('/'); + String srcPath = path.substring(0, lastSlash); + if (srcPath.equals("main:")) + { + srcPath = srcPath + "/"; + } + String srcName = path.substring(lastSlash + 1); + String dstPath = RandomDirectory(); + try + { + System.out.println("rename " + srcPath + " " + srcName + " " + dstPath + " " + name); + fService.rename(srcPath, srcName, dstPath, name); + RemovePath(path); + if (desc.isDirectory()) + { + AddDirectory(appendPath(dstPath, name)); + } + else + { + AddFile(appendPath(dstPath, name)); + } + } + catch (AVMException ae) + { + if (ae instanceof AVMExistsException || + ae instanceof AVMNotFoundException || + ae instanceof AVMWrongTypeException || + ae instanceof AVMCycleException) + { + return; + } + throw ae; + } + } + + private void createLayeredDir() + { + String name = "LD" + fNames[fgRandom.nextInt(26 * 26)]; + String path = RandomDirectory(); + String target = RandomDirectory(); + try + { + System.out.println("mklayereddir " + path + " " + name + " " + target); + fService.createLayeredDirectory(target, path, name); + AddDirectory(appendPath(path, name)); + } + catch (AVMException ae) + { + if (ae instanceof AVMExistsException || + ae instanceof AVMNotFoundException || + ae instanceof AVMWrongTypeException || + ae instanceof AVMCycleException) + { + return; + } + throw ae; + } + } + + private void createLayeredFile() + { + String name = "LF" + fNames[fgRandom.nextInt(26 * 26)]; + String path = RandomDirectory(); + String target = RandomFile(); + try + { + System.out.println("createlayered " + path + " " + name + " " + target); + fService.createLayeredFile(target, path, name); + AddFile(appendPath(path, name)); + } + catch (AVMException ae) + { + if (ae instanceof AVMExistsException || + ae instanceof AVMNotFoundException || + ae instanceof AVMWrongTypeException || + ae instanceof AVMCycleException) + { + return; + } + throw ae; + } + } + + private void removeNode() + { + String target = RandomPath(); + int lastSlash = target.lastIndexOf('/'); + String path = target.substring(0, lastSlash); + if (path.equals("main:")) + { + path = path + "/"; + } + String name = target.substring(lastSlash + 1); + try + { + System.out.println("remove " + target); + fService.removeNode(path, name); + RemovePath(target); + } + catch (AVMException e) + { + if (e instanceof AVMNotFoundException || + e instanceof AVMWrongTypeException || + e instanceof AVMCycleException) + { + return; + } + throw e; + } + } + + private void modifyFile() + { + String path = RandomFile(); + try + { + System.out.println("modify " + path); + PrintStream out = + new PrintStream(fService.getFileOutputStream(path)); + out.println("I am " + path); + out.close(); + } + catch (AVMException e) + { + if (e instanceof AVMNotFoundException || + e instanceof AVMWrongTypeException || + e instanceof AVMCycleException) + { + return; + } + throw e; + } + } + + private void readFile() + { + String path = RandomFile(); + try + { + System.out.println("read " + path); + BufferedReader reader = + new BufferedReader(new InputStreamReader(fService.getFileInputStream(-1, path))); + String line = reader.readLine(); + System.out.println(line); + reader.close(); + } + catch (AVMException e) + { + if (e instanceof AVMNotFoundException || + e instanceof AVMWrongTypeException || + e instanceof AVMCycleException) + { + return; + } + throw e; + } + catch (IOException e) + { + throw new AVMException("I/O Error.", e); + } + } + + public void Refresh() + { + System.out.println("refresh"); + fgAllPaths = new ArrayList(); + fgAllDirectories = new ArrayList(); + fgAllFiles = new ArrayList(); + fgAllPaths.add("main:/"); + fgAllDirectories.add("main:/"); + Set visited = new HashSet(); + AVMNodeDescriptor root = fService.getRepositoryRoot(-1, "main"); + RecursiveRefresh(root, visited); + } + + private void RecursiveRefresh(AVMNodeDescriptor dir, Set visited) + { + try + { + String baseName = dir.getPath().endsWith("/") ? dir.getPath() : dir.getPath() + "/"; + Map listing = fService.getDirectoryListing(dir); + for (String name : listing.keySet()) + { + String path = baseName + name; + AVMNodeDescriptor desc = listing.get(name); + switch (desc.getType()) + { + case AVMNodeType.LAYERED_DIRECTORY : + case AVMNodeType.PLAIN_DIRECTORY : + { + if (visited.contains(desc.getId())) + { + continue; + } + visited.add(desc.getId()); + fgAllPaths.add(path); + fgAllDirectories.add(path); + RecursiveRefresh(desc, visited); + break; + } + case AVMNodeType.LAYERED_FILE : + case AVMNodeType.PLAIN_FILE : + { + fgAllPaths.add(path); + fgAllFiles.add(path); + break; + } + } + } + } + catch (AVMException e) + { + if (e instanceof AVMNotFoundException || + e instanceof AVMWrongTypeException || + e instanceof AVMCycleException) + { + return; + } + throw e; + } + } + + private void snapshot() + { + System.out.println("snapshot"); + fService.createSnapshot("main"); + } + + public boolean getError() + { + return fError; + } + + public void setExit() + { + fExit = true; + } + + private static synchronized void AddDirectory(String path) + { + fgAllDirectories.add(path); + fgAllPaths.add(path); + } + + private static synchronized void AddFile(String path) + { + fgAllFiles.add(path); + fgAllPaths.add(path); + } + + private static synchronized void RemovePath(String path) + { + List allPaths = new ArrayList(); + List allDirectories = new ArrayList(); + List allFiles = new ArrayList(); + for (String p : fgAllPaths) + { + if (p.indexOf(path) != 0) + { + allPaths.add(p); + } + } + for (String p : fgAllDirectories) + { + if (p.indexOf(path) != 0) + { + allDirectories.add(p); + } + } + for (String p : fgAllFiles) + { + if (p.indexOf(path) != 0) + { + allFiles.add(p); + } + } + fgAllPaths = allPaths; + fgAllDirectories = allDirectories; + fgAllFiles = allFiles; + } + + private String appendPath(String path, String name) + { + return path.endsWith("/") ? path + name : path + "/" + name; + } + + private static synchronized String RandomDirectory() + { + return fgAllDirectories.get(fgRandom.nextInt(fgAllDirectories.size())); + } + + private static synchronized String RandomFile() + { + return fgAllFiles.get(fgRandom.nextInt(fgAllFiles.size())); + } + + private static synchronized String RandomPath() + { + return fgAllPaths.get(fgRandom.nextInt(fgAllPaths.size())); + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMWrongTypeException.java b/source/java/org/alfresco/repo/avm/AVMWrongTypeException.java new file mode 100644 index 0000000000..8562f95fc6 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMWrongTypeException.java @@ -0,0 +1,68 @@ +/* + * 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; + +/** + * Thrown when an object of the wrong type is looked up. + * @author britt + */ +public class AVMWrongTypeException extends AVMException +{ + private static final long serialVersionUID = -8799318236851345536L; + + /** + * @param msgId + */ + public AVMWrongTypeException(String msgId) + { + super(msgId); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + */ + public AVMWrongTypeException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param cause + */ + public AVMWrongTypeException(String msgId, Throwable cause) + { + super(msgId, cause); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public AVMWrongTypeException(String msgId, Object[] msgParams, + Throwable cause) + { + super(msgId, msgParams, cause); + // TODO Auto-generated constructor stub + } +} diff --git a/source/java/org/alfresco/repo/avm/BasicAttributes.java b/source/java/org/alfresco/repo/avm/BasicAttributes.java new file mode 100644 index 0000000000..5336e97ecf --- /dev/null +++ b/source/java/org/alfresco/repo/avm/BasicAttributes.java @@ -0,0 +1,83 @@ +/** + * + */ +package org.alfresco.repo.avm; + +/** + * Ownership, timestamps, later perhaps ACLs + * @author britt + */ +public interface BasicAttributes +{ + /** + * Set the creator of the node. + * @param creator The creator to set. + */ + public void setCreator(String creator); + + /** + * Get the creator of the node. + * @return The creator. + */ + public String getCreator(); + + /** + * Set the owner of the node. + * @param owner The owner to set. + */ + public void setOwner(String owner); + + /** + * Get the owner of the node. + * @return The owner. + */ + public String getOwner(); + + /** + * Set the last modifier of the node. + * @param lastModifier + */ + public void setLastModifier(String lastModifier); + + /** + * Get the last modifier of the node. + * @return The last modifier. + */ + public String getLastModifier(); + + /** + * Set the create date. + * @param createDate The date to set. + */ + public void setCreateDate(long createDate); + + /** + * Get the create date. + * @return The create date. + */ + public long getCreateDate(); + + /** + * Set the modification date. + * @param modDate The date to set. + */ + public void setModDate(long modDate); + + /** + * Get the modification date. + * @return The modification date. + */ + public long getModDate(); + + /** + * Set the access date of the node. + * @param accessDate The access date. + */ + public void setAccessDate(long accessDate); + + /** + * Get the access date of the node. + * @return The access date. + */ + public long getAccessDate(); +} diff --git a/source/java/org/alfresco/repo/avm/BasicAttributesImpl.java b/source/java/org/alfresco/repo/avm/BasicAttributesImpl.java new file mode 100644 index 0000000000..d888c5b6cc --- /dev/null +++ b/source/java/org/alfresco/repo/avm/BasicAttributesImpl.java @@ -0,0 +1,200 @@ +/** + * + */ +package org.alfresco.repo.avm; + +import java.io.Serializable; + + +/** + * Implementation of the BasicAttributesBean. + * @author britt + */ +public class BasicAttributesImpl implements BasicAttributes, Serializable +{ + private static final long serialVersionUID = -3796354564923670005L; + + /** + * The creator. + */ + private String fCreator; + + /** + * The owner. + */ + private String fOwner; + + /** + * The last modifier. + */ + private String fLastModifier; + + /** + * The creation date. + */ + private long fCreateDate; + + /** + * The modification date. + */ + private long fModDate; + + /** + * The access date. + */ + private long fAccessDate; + + /** + * Default constructor. + */ + public BasicAttributesImpl() + { + } + + /** + * A Copy constructor. + * @param other + */ + public BasicAttributesImpl(BasicAttributes other) + { + fCreator = other.getCreator(); + fOwner = other.getOwner(); + fLastModifier = other.getLastModifier(); + fCreateDate = other.getCreateDate(); + fModDate = other.getModDate(); + fAccessDate = other.getAccessDate(); + } + + /** + * Fill in the blanks constructor. + * @param creator + * @param owner + * @param modifier + * @param createDate + * @param modDate + * @param accessDate + */ + public BasicAttributesImpl(String creator, + String owner, + String modifier, + long createDate, + long modDate, + long accessDate) + { + fCreator = creator; + fOwner = owner; + fLastModifier = modifier; + fCreateDate = createDate; + fModDate = modDate; + fAccessDate = accessDate; + } + + /** + * Set the creator. + * @param creator + */ + public void setCreator(String creator) + { + fCreator = creator; + } + + /** + * Get the creator. + * @return The creator. + */ + public String getCreator() + { + return fCreator; + } + + /** + * Set the owner. + * @param owner + */ + public void setOwner(String owner) + { + fOwner = owner; + } + + /** + * Get the owner. + * @return The owner. + */ + public String getOwner() + { + return fOwner; + } + + /** + * Set the last modifier. + * @param lastModifier + */ + public void setLastModifier(String lastModifier) + { + fLastModifier = lastModifier; + } + + /** + * Get the last modifier. + * @return The last modifier. + */ + public String getLastModifier() + { + return fLastModifier; + } + + /** + * Set the create date. + * @param createDate + */ + public void setCreateDate(long createDate) + { + fCreateDate = createDate; + } + + /** + * Get the create date. + * @return The create date. + */ + public long getCreateDate() + { + return fCreateDate; + } + + /** + * Set the modification date. + * @param modDate + */ + public void setModDate(long modDate) + { + fModDate = modDate; + } + + /** + * Get the modification date. + * @return modDate + */ + public long getModDate() + { + return fModDate; + } + + // TODO Do we want this? + /** + * Set the access date. + * @param accessDate + */ + public void setAccessDate(long accessDate) + { + fAccessDate = accessDate; + } + + /** + * Get the access date. + * @return The access date. + */ + public long getAccessDate() + { + return fAccessDate; + } +} diff --git a/source/java/org/alfresco/repo/avm/ChildEntry.java b/source/java/org/alfresco/repo/avm/ChildEntry.java new file mode 100644 index 0000000000..6f3b4a7e39 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/ChildEntry.java @@ -0,0 +1,61 @@ +/* + * 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; + +/** + * This interface represents an entry in a directory. + * @author britt + */ +public interface ChildEntry +{ + /** + * Set the name of the child. + * @param name + */ + public void setName(String name); + + /** + * Get the name of the child. + * @return The child's name. + */ + public String getName(); + + /** + * Set the parent in this entry. + * @param parent + */ + public void setParent(DirectoryNode parent); + + /** + * Get the parent of this child. + * @return The parent. + */ + public DirectoryNode getParent(); + + /** + * Set the child in this entry. + * @param child + */ + public void setChild(AVMNode child); + + /** + * Get the child in this entry. + * @return The child. + */ + public AVMNode getChild(); +} diff --git a/source/java/org/alfresco/repo/avm/ChildEntryImpl.java b/source/java/org/alfresco/repo/avm/ChildEntryImpl.java new file mode 100644 index 0000000000..989cc0df87 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/ChildEntryImpl.java @@ -0,0 +1,148 @@ +/* + * 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.Serializable; + +/** + * An entry in a directory. Contains a name, parent, and child. + * @author britt + */ +public class ChildEntryImpl implements ChildEntry, Serializable +{ + private static final long serialVersionUID = -307752114272916930L; + + /** + * The name of the entry. + */ + private String fName; + + /** + * The parent. + */ + private DirectoryNode fParent; + + /** + * The child. + */ + private AVMNode fChild; + + /** + * Default constructor for Hibernate. + */ + protected ChildEntryImpl() + { + } + + /** + * Make up a brand new entry. + * @param name + * @param parent + * @param child + */ + public ChildEntryImpl(String name, + DirectoryNode parent, + AVMNode child) + { + fName = name; + fParent = parent; + fChild = child; + } + + /** + * Set the name of this entry. + * @param name + */ + public void setName(String name) + { + fName = name; + } + + /** + * Get the name of this entry. + * @return + */ + public String getName() + { + return fName; + } + + /** + * Set the parent in this entry. + * @param parent + */ + public void setParent(DirectoryNode parent) + { + fParent = parent; + } + + /** + * Get the parent in this entry. + * @return + */ + public DirectoryNode getParent() + { + return fParent; + } + + /** + * Set the child in this entry. + * @param child + */ + public void setChild(AVMNode child) + { + fChild = child; + } + + /** + * Get the child in this entry. + * @return + */ + public AVMNode getChild() + { + return fChild; + } + + /** + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof ChildEntry)) + { + return false; + } + ChildEntry other = (ChildEntry)obj; + return fName.equals(other.getName()) && fParent.equals(other.getParent()); + } + + /** + * @return + */ + @Override + public int hashCode() + { + return fName.hashCode() + fParent.hashCode(); + } +} diff --git a/source/java/org/alfresco/repo/avm/DeletedChild.java b/source/java/org/alfresco/repo/avm/DeletedChild.java new file mode 100644 index 0000000000..73e8206a7d --- /dev/null +++ b/source/java/org/alfresco/repo/avm/DeletedChild.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * Interface to a deleted directory entry in a layered directory. + * @author britt + */ +public interface DeletedChild +{ + /** + * Get the name of the deleted child. + * @return The name. + */ + public String getName(); + + /** + * Get the parent of this deleted child + * @return The parent. + */ + public LayeredDirectoryNode getParent(); +} diff --git a/source/java/org/alfresco/repo/avm/DeletedChildImpl.java b/source/java/org/alfresco/repo/avm/DeletedChildImpl.java new file mode 100644 index 0000000000..07909636a3 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/DeletedChildImpl.java @@ -0,0 +1,123 @@ +/* + * 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.Serializable; + +/** + * Represents a deleted child in a layered directory. + * @author britt + */ +public class DeletedChildImpl implements DeletedChild, Serializable +{ + private static final long serialVersionUID = 4997060636280774719L; + + /** + * The name of the deleted child. + */ + private String fName; + + /** + * The parent directory. + */ + private LayeredDirectoryNode fParent; + + /** + * Default constructor. For Hibernate. + */ + protected DeletedChildImpl() + { + } + + /** + * Create a new one. + * @param name + * @param parent + */ + public DeletedChildImpl(String name, + LayeredDirectoryNode parent) + { + fName = name; + fParent = parent; + } + + /** + * Set the name of the deleted child. For Hibernate. + * @param name + */ + protected void setName(String name) + { + fName = name; + } + + /** + * Get the name of the deleted child. + * @return The name. + */ + public String getName() + { + return fName; + } + + /** + * Set the parent directory. + * @param parent + */ + protected void setParent(LayeredDirectoryNode parent) + { + fParent = parent; + } + + /** + * Get the parent of the deleted child. + * @return The parent. + */ + public LayeredDirectoryNode getParent() + { + return fParent; + } + + /** + * Equality in the database entity sense. + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof DeletedChild)) + { + return false; + } + DeletedChild dc = (DeletedChild)obj; + return fParent.equals(dc.getParent()) && fName.equals(dc.getName()); + } + + /** + * @return A hash code. + */ + @Override + public int hashCode() + { + return fParent.hashCode() + fName.hashCode(); + } +} diff --git a/source/java/org/alfresco/repo/avm/DirectoryNode.java b/source/java/org/alfresco/repo/avm/DirectoryNode.java index 9d31e632ee..817830e787 100644 --- a/source/java/org/alfresco/repo/avm/DirectoryNode.java +++ b/source/java/org/alfresco/repo/avm/DirectoryNode.java @@ -14,41 +14,48 @@ * language governing permissions and limitations under the * License. */ - package org.alfresco.repo.avm; +import java.util.List; import java.util.Map; -import org.alfresco.repo.avm.hibernate.DirectoryEntry; - /** - * Base class for Directories. + * The interface for Directory Nodes. * @author britt */ -public abstract class DirectoryNode extends AVMNode +interface DirectoryNode extends AVMNode { /** * Does this directory directly contain the specified node. * @param node The node to check. * @return Whether it does. */ - public abstract boolean directlyContains(AVMNode node); - + public boolean directlyContains(AVMNode node); + /** * Put child into this directory directly. No copy on write. * @param name The name to give it. * @param node The child. */ - public abstract void putChild(String name, AVMNode node); - + public void putChild(String name, AVMNode node); + /** * Lookup a child node. * @param lPath The Lookup so far. * @param name The name of the child to lookup. * @param version The version to look under. + * @param visited A Set of full paths visited. Used for cycle checking. */ - public abstract AVMNode lookupChild(Lookup lPath, String name, int version); + public AVMNode lookupChild(Lookup lPath, String name, int version); + /** + * Lookup a child node using an AVMNodeDescriptor as context. + * @param mine The node descriptor for this. + * @param name The name of the child to lookup. + * @return The descriptor for the looked up child. + */ + public AVMNodeDescriptor lookupChild(AVMNodeDescriptor mine, String name); + /** * Add a child node. Fails if child already exists. * Copy is possible. @@ -56,28 +63,60 @@ public abstract class DirectoryNode extends AVMNode * @param child The child to add. * @param The lookup path. */ - public abstract boolean addChild(String name, AVMNode child, - Lookup lPath); - + public boolean addChild(String name, AVMNode child, Lookup lPath); + /** * Remove a child node. Fails if child does not exist. * Copy is possible. * @param name The name of the child to remove. * @param lPath The lookup path. */ - public abstract boolean removeChild(String name, Lookup lPath); - + public boolean removeChild(String name, Lookup lPath); + /** * Remove a child directly. No copy is possible. * @param name The name of the child to remove. */ - public abstract void rawRemoveChild(String name); - + public void rawRemoveChild(String name); + /** * Get a directory listing. * @param lPath The lookup context. * @param version The version to look under. * @return A Map of names to DirectoryEntries. */ - public abstract Map getListing(Lookup lPath, int version); -} + public Map getListing(Lookup lPath); + + /** + * Get a listing from a directory specified by an AVMNodeDescriptor. + * @param dir The directory to list. + * @return A Map of names to node descriptors + */ + public Map getListing(AVMNodeDescriptor dir); + + /** + * Set the directory, which must be in a layer, into a primary + * indirection taking its indirection from the Lookup. + * @param lPath The Lookup. + */ + public void turnPrimary(Lookup lPath); + + /** + * Retarget a layered directory. + * @param lPath The Lookup. + * @param target The target path. + */ + public void retarget(Lookup lPath, String target); + + /** + * Get all the directly contained children of a node. + * @return A List of Child entries. + */ + public List getChildren(); + + /** + * Set whether this node is a root node. + * @param isRoot + */ + public void setIsRoot(boolean isRoot); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/DirectoryNodeImpl.java b/source/java/org/alfresco/repo/avm/DirectoryNodeImpl.java new file mode 100644 index 0000000000..33ffbe0a28 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/DirectoryNodeImpl.java @@ -0,0 +1,100 @@ +/* + * 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.util.List; + +import org.hibernate.Query; +import org.hibernate.Session; + +/** + * Base class for Directories. + * @author britt + */ +abstract class DirectoryNodeImpl extends AVMNodeImpl implements DirectoryNode +{ + /** + * Default constructor. + */ + protected DirectoryNodeImpl() + { + } + + /** + * A pass through constructor. Called when a new concrete subclass + * instance is created. + * @param id + * @param repo + */ + protected DirectoryNodeImpl(long id, Repository repo) + { + super(id, repo); + } + + /** + * Retrieves the ChildEntry in this directory with the given name. + * @param name The name to look for. + * @return The ChildEntry or null if not found. + */ + @SuppressWarnings("unchecked") + protected ChildEntry getChild(String name) + { + Session sess = SuperRepository.GetInstance().getSession(); + return (ChildEntry)sess.get(ChildEntryImpl.class, new ChildEntryImpl(name, this, null)); + } + + /** + * Get all the children of this directory. NB, this should + * really be considered an internal method but it needs to be + * exposed through the interface. + * @return A List of ChildEntries. + */ + @SuppressWarnings("unchecked") + public List getChildren() + { + Session sess = SuperRepository.GetInstance().getSession(); + Query query = sess.getNamedQuery("ChildEntry.ByParent"); + query.setEntity("parent", this); + query.setCacheable(true); + query.setCacheRegion("ChildEntry.ByParent"); + List found = (List)query.list(); + return found; + } + + /** + * Get the ChildEntry that has the given child. + * @param child The child node to look for. + * @return The ChildEntry or null if not found. + */ + @SuppressWarnings("unchecked") + protected ChildEntry getChild(AVMNode child) + { + Session sess = SuperRepository.GetInstance().getSession(); + Query query = sess.getNamedQuery("ChildEntry.ByParentChild"); + query.setEntity("parent", this); + query.setEntity("child", child); + query.setCacheable(true); + query.setCacheRegion("ChildEntry.ByParentChild"); + List found = (List)query.list(); + if (found.size() == 0) + { + return null; + } + return found.get(0); + } +} diff --git a/source/java/org/alfresco/repo/avm/FileContent.java b/source/java/org/alfresco/repo/avm/FileContent.java index 23a0728977..c493faec01 100644 --- a/source/java/org/alfresco/repo/avm/FileContent.java +++ b/source/java/org/alfresco/repo/avm/FileContent.java @@ -14,213 +14,62 @@ * language governing permissions and limitations under the * License. */ - package org.alfresco.repo.avm; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Formatter; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.avm.hibernate.ContentBean; -import org.alfresco.repo.avm.hibernate.ContentBeanImpl; +import java.io.RandomAccessFile; /** - * Content that is readable and writeable. + * Interface for file content. FileContent can be shared between files. * @author britt */ -public class FileContent +interface FileContent { - /** - * The data containing bean. - */ - private ContentBean fData; - - /** - * The name of the file. - */ - private String fName; - - /** - * The directory path of the file. - */ - private String fPath; - - /** - * Make one from a bean. - * @param data The Bean with the data. - */ - public FileContent(ContentBean data) - { - fData = data; - } - - /** - * Make a brand new one. - * @param superRepo The SuperRepository. - */ - public FileContent(SuperRepository superRepo) - { - fData = new ContentBeanImpl(superRepo.issueContentID()); - fData.setRefCount(1); - // Make an empty file. - try - { - getOutputStream(superRepo).close(); - } - catch (IOException ie) - { - throw new AlfrescoRuntimeException("Couldn't close file.", ie); - } - superRepo.getSession().save(fData); - } - - /** - * Copy constructor, sort of. - * @param other The content to copy from. - * @param superRepo The SuperRepository. - */ - public FileContent(FileContent other, SuperRepository superRepo) - { - fData = new ContentBeanImpl(superRepo.issueContentID()); - fData.setRefCount(1); - // Copy the contents from other to this. - BufferedInputStream in = new BufferedInputStream(other.getInputStream(superRepo)); - BufferedOutputStream out = new BufferedOutputStream(this.getOutputStream(superRepo)); - try - { - byte [] buff = new byte[4096]; // Nyah, nyah. - int bytesRead; - while ((bytesRead = in.read(buff)) != -1) - { - out.write(buff, 0, bytesRead); - } - out.close(); - in.close(); - } - catch (IOException ie) - { - throw new AlfrescoRuntimeException("I/O failure in Copy on Write.", ie); - } - superRepo.getSession().save(fData); - } - /** * Get the number of files that refer to this content. * @return The reference count. */ - public int getRefCount() - { - return fData.getRefCount(); - } + public int getRefCount(); /** * Set the reference count. * @param count The count to set. */ - public void setRefCount(int count) - { - fData.setRefCount(count); - } + public void setRefCount(int count); /** * Get an input stream from the content. * @param superRepo The SuperRepository. * @return An InputStream. */ - public InputStream getInputStream(SuperRepository superRepo) - { - try - { - return new FileInputStream(getContentPath(superRepo)); - } - catch (IOException ie) - { - throw new AlfrescoRuntimeException("Could not open for reading: " + getContentPath(superRepo), ie); - } - } + public InputStream getInputStream(SuperRepository superRepo); /** * Get an output stream to the content. * @param superRepo The SuperRepository. * @return an OutputStream. */ - public OutputStream getOutputStream(SuperRepository superRepo) - { - try - { - File dir = new File(getDirectoryPath(superRepo)); - if (!dir.exists()) - { - dir.mkdirs(); - } - return new FileOutputStream(getContentPath(superRepo)); - } - catch (IOException ie) - { - throw new AlfrescoRuntimeException("Could not open for writing: " + getContentPath(superRepo), ie); - } - } - - /** - * Get the underlying data bean. Don't abuse the privilege. - * @return The data bean. - */ - public ContentBean getDataBean() - { - return fData; - } + public OutputStream getOutputStream(SuperRepository superRepo); /** - * Retrieve the full path for this content. + * Get a random access file to this content. + * @param superRepo The SuperRepository. + * @param access The mode to open the file in. + * @return A RandomAccessFile. + */ + public RandomAccessFile getRandomAccess(SuperRepository superRepo, String access); + + /** + * Get the length of the file. * @param superRepo - * @return The full path for this content. + * @return The length of the file. */ - private String getContentPath(SuperRepository superRepo) - { - if (fName == null) - { - calcPathData(superRepo); - } - return fName; - } + public long getLength(SuperRepository superRepo); /** - * Get the directory path for this content. - * @param superRepo - * @return The directory path. + * Get the object id. + * @return object id. */ - private String getDirectoryPath(SuperRepository superRepo) - { - if (fPath == null) - { - calcPathData(superRepo); - } - return fPath; - } - - /** - * Calculate the path data. - */ - private void calcPathData(SuperRepository superRepo) - { - long id = fData.getId(); - Formatter form = new Formatter(new StringBuilder()); - form.format("%016x", id); - String name = form.toString(); - form = new Formatter(new StringBuilder()); - form.format("/%02x/%02x/%02x", - (id & 0xff000000) >> 24, - (id & 0xff0000) >> 16, - (id & 0xff00) >> 8); - String dir = form.toString(); - fPath = superRepo.getStorageRoot() + dir; - fName = fPath + "/" + name; - } -} + public long getId(); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/FileContentImpl.java b/source/java/org/alfresco/repo/avm/FileContentImpl.java new file mode 100644 index 0000000000..96268cdd4c --- /dev/null +++ b/source/java/org/alfresco/repo/avm/FileContentImpl.java @@ -0,0 +1,325 @@ +/* + * 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.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.util.Formatter; + +/** + * Content that is readable and writeable. + * @author britt + */ +class FileContentImpl implements FileContent, Serializable +{ + static final long serialVersionUID = -7450825236235397307L; + + /** + * The Object ID. + */ + private long fID; + + /** + * The reference count of this FileContent. + */ + private int fRefCount; + + /** + * The version (for concurrency control). + */ + private long fVers; + + /** + * The name of the file. + */ + private String fName; + + /** + * The directory path of the file. + */ + private String fPath; + + /** + * Default constructor. + */ + public FileContentImpl() + { + fName = null; + fPath = null; + } + + /** + * Make a brand new one. + * @param superRepo The SuperRepository. + * @param source A possibly null stream to get data from. + */ + public FileContentImpl(SuperRepository superRepo) + { + fID = superRepo.issueContentID(); + fRefCount = 1; + // Initialize the contents. + try + { + OutputStream out = getOutputStream(superRepo); + out.close(); + } + catch (IOException ie) + { + throw new AVMException("File data error.", ie); + } + superRepo.getSession().save(this); + } + + /** + * Copy constructor, sort of. + * @param other The content to copy from. + * @param superRepo The SuperRepository. + */ + public FileContentImpl(FileContent other, SuperRepository superRepo) + { + fID = superRepo.issueContentID(); + fRefCount = 1; + // Copy the contents from other to this. + BufferedInputStream in = new BufferedInputStream(other.getInputStream(superRepo)); + BufferedOutputStream out = new BufferedOutputStream(this.getOutputStream(superRepo)); + try + { + byte [] buff = new byte[4096]; // Nyah, nyah. + int bytesRead; + while ((bytesRead = in.read(buff)) != -1) + { + out.write(buff, 0, bytesRead); + } + out.flush(); + out.close(); + in.close(); + } + catch (IOException ie) + { + throw new AVMException("I/O failure in Copy on Write.", ie); + } + superRepo.getSession().save(this); + } + + /** + * Get this FileContent's reference count. + * @return The reference count. + */ + public int getRefCount() + { + return fRefCount; + } + + /** + * Set the reference count on this. + * @param count The reference count to set. + */ + public void setRefCount(int count) + { + fRefCount = count; + } + + /** + * Get an InputStream from this FileContent. + * @param superRepo The SuperRepository (to get backing store location from). + * @return An InputStream. + */ + public InputStream getInputStream(SuperRepository superRepo) + { + try + { + return new FileInputStream(getContentPath(superRepo)); + } + catch (IOException ie) + { + throw new AVMException("Could not open for reading: " + getContentPath(superRepo), ie); + } + } + + /** + * Gets an ouptut stream to this node. + * @param superRepo The SuperRepository. + * @return An OutputStream. + */ + public OutputStream getOutputStream(SuperRepository superRepo) + { + try + { + File dir = new File(getDirectoryPath(superRepo)); + if (!dir.exists()) + { + dir.mkdirs(); + } + return new FileOutputStream(getContentPath(superRepo)); + } + catch (IOException ie) + { + throw new AVMException("Could not open for writing: " + getContentPath(superRepo), ie); + } + } + + /** + * Get a random access file from this content. It's the responsibility of + * the caller of this to insure that this object has been copied if the + * access argument is a write mode. + * @param superRepo The SuperRepository. + * @param access The access more for RandomAccessFile. + * @return A RandomAccessFile. + */ + public RandomAccessFile getRandomAccess(SuperRepository superRepo, String access) + { + try + { + return new RandomAccessFile(getContentPath(superRepo), access); + } + catch (IOException ie) + { + throw new AVMException("Could not open for random access: " + getContentPath(superRepo), ie); + } + } + + /** + * Get the length of this content. + * @param superRepo The SuperRepository + * @return The length of the content. + */ + public long getLength(SuperRepository superRepo) + { + File file = new File(getContentPath(superRepo)); + return file.length(); + } + + /** + * Retrieve the full path for this content. + * @param superRepo + * @return The full path for this content. + */ + private synchronized String getContentPath(SuperRepository superRepo) + { + if (fName == null) + { + calcPathData(superRepo); + } + return fName; + } + + /** + * Get the directory path for this content. + * @param superRepo + * @return The directory path. + */ + private synchronized String getDirectoryPath(SuperRepository superRepo) + { + if (fPath == null) + { + calcPathData(superRepo); + } + return fPath; + } + + /** + * Calculate the path data. + */ + private void calcPathData(SuperRepository superRepo) + { + Formatter form = new Formatter(new StringBuilder()); + form.format("%016x", fID); + String name = form.toString(); + form = new Formatter(new StringBuilder()); + form.format("/%02x/%02x/%02x", + (fID & 0xff000000) >> 24, + (fID & 0xff0000) >> 16, + (fID & 0xff00) >> 8); + String dir = form.toString(); + fPath = superRepo.getStorageRoot() + dir; + fName = fPath + "/" + name; + } + + /** + * Set the version for concurrency control. + * @param vers The value to set. + */ + protected void setVers(long vers) + { + fVers = vers; + } + + /** + * Get the version for concurrency control. + * @return The version. + */ + protected long getVers() + { + return fVers; + } + + /** + * Set the object id. For Hibernate. + * @param id + */ + protected void setId(long id) + { + fID = id; + } + + /** + * Get the object id. + * @return The object id. + */ + public long getId() + { + return fID; + } + + /** + * Equals predicate. Based on object ID. + * @param obj The obect to compare against. + * @return + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof FileContent)) + { + return false; + } + return fID == ((FileContent)obj).getId(); + } + + /** + * Generate a hashCode. + * @return The hashCode. + */ + @Override + public int hashCode() + { + return (int)fID; + } +} diff --git a/source/java/org/alfresco/repo/avm/FileNode.java b/source/java/org/alfresco/repo/avm/FileNode.java index 31f3b8ac97..f1124bb80a 100644 --- a/source/java/org/alfresco/repo/avm/FileNode.java +++ b/source/java/org/alfresco/repo/avm/FileNode.java @@ -14,25 +14,26 @@ * language governing permissions and limitations under the * License. */ - package org.alfresco.repo.avm; /** - * Base class for file objects. + * Interface for the generic idea of a file. * @author britt */ -public abstract class FileNode extends AVMNode +interface FileNode extends AVMNode { /** * Get the content object associated with this node, for reading. * @param version The version to get in. * @return A FileContent object. */ - public abstract FileContent getContentForRead(int version, Repository repo); - + public FileContent getContentForRead(Repository repo); + /** * Get the content object for writing. This will do COW * as needed. + * @param The repository. + * @return A FileContent object. */ - public abstract FileContent getContentForWrite(Repository repo); -} + public FileContent getContentForWrite(Repository repo); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/FileNodeImpl.java b/source/java/org/alfresco/repo/avm/FileNodeImpl.java new file mode 100644 index 0000000000..8bccd43183 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/FileNodeImpl.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * Base class for file objects. + * @author britt + */ +abstract class FileNodeImpl extends AVMNodeImpl implements FileNode +{ + /** + * Default constructor. + */ + protected FileNodeImpl() + { + } + + /** + * Pass through constructor. + * @param id The newly assigned object id. + * @param repo The repository we belong to. + */ + public FileNodeImpl(long id, Repository repo) + { + super(id, repo); + } +} diff --git a/source/java/org/alfresco/repo/avm/Issuer.java b/source/java/org/alfresco/repo/avm/Issuer.java new file mode 100644 index 0000000000..b9ad2d07ab --- /dev/null +++ b/source/java/org/alfresco/repo/avm/Issuer.java @@ -0,0 +1,149 @@ +/* + * 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.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * This is a helper class that knows how to issue identifiers. + * @author britt + */ +class Issuer +{ + /** + * The path to this issuers persistent storage. + */ + private String fPath; + + /** + * The next number to issue. + */ + private long fNext; + + /** + * Constructor for an already existing Issuer. + * @param path The path to this issuers persistent store. + */ + public Issuer(String path) + { + fPath = path; + try + { + DataInputStream in = new DataInputStream(new FileInputStream(fPath + ".new")); + fNext = in.readLong(); + fNext += 257; + in.close(); + save(); + return; + } + catch (IOException ie) + { + // Do nothing. + } + try + { + DataInputStream in = new DataInputStream(new FileInputStream(fPath)); + fNext = in.readLong(); + fNext += 257; + in.close(); + save(); + return; + } + catch (IOException ie) + { + // Do nothing. + } + // Last resort. + try + { + DataInputStream in = new DataInputStream(new FileInputStream(fPath + ".old")); + fNext = in.readLong(); + fNext += 513; + in.close(); + save(); + return; + } + catch (IOException ie) + { + // TODO Log this situation. + throw new AVMException("Could not restore issuer" + fPath, ie); + } + } + + /** + * Rich constructor. + * @param path The path to this issuers persistent store. + * @param next The next number to issue. + */ + public Issuer(String path, long next) + { + fPath = path; + fNext = next; + save(); + } + + /** + * Issue the next number. + * @return A serial number. + */ + public synchronized long issue() + { + long val = fNext++; + if (fNext % 128 == 0) + { + save(); + } + return val; + } + + /** + * Persist this issuer. + */ + public void save() + { + while (true) + { + try + { + FileOutputStream fileOut = new FileOutputStream(fPath + ".new"); + DataOutputStream out = new DataOutputStream(fileOut); + out.writeLong(fNext); + out.flush(); + // Force data to physical storage. + fileOut.getChannel().force(true); + out.close(); + File from = new File(fPath); + File to = new File(fPath + ".old"); + from.renameTo(to); + from = new File(fPath + ".new"); + to = new File(fPath); + from.renameTo(to); + break; + } + catch (IOException ie) + { + // TODO Log this situation. + } + } + } +} diff --git a/source/java/org/alfresco/repo/avm/IssuerTest.java b/source/java/org/alfresco/repo/avm/IssuerTest.java new file mode 100644 index 0000000000..23ab13cf7a --- /dev/null +++ b/source/java/org/alfresco/repo/avm/IssuerTest.java @@ -0,0 +1,41 @@ +/* + * 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 junit.framework.TestCase; + +/** + * Simple sanity test for Issuer; + * @author britt + */ +public class IssuerTest extends TestCase +{ + /** + * Sanity check issuer logic. + */ + public void testSanity() + { + Issuer issuer = new Issuer("test", 0L); + for (int i = 0; i < 500; i++) + { + issuer.issue(); + } + issuer = new Issuer("test"); + assertTrue(issuer.issue() >= 500); + } +} diff --git a/source/java/org/alfresco/repo/avm/Layered.java b/source/java/org/alfresco/repo/avm/Layered.java index d527451e26..ac7da5a77d 100644 --- a/source/java/org/alfresco/repo/avm/Layered.java +++ b/source/java/org/alfresco/repo/avm/Layered.java @@ -7,7 +7,7 @@ package org.alfresco.repo.avm; * Layered nodes share this method. * @author britt */ -public interface Layered +interface Layered { /** * Get the indirection, or underlying path that this @@ -17,4 +17,11 @@ public interface Layered * @return */ public String getUnderlying(Lookup lookup); + + /** + * Get the raw indirection of a layered node. + * @return The raw indirection, which will be null for + * LayeredDirectoryNodes that are not primary indirections. + */ + public String getIndirection(); } diff --git a/source/java/org/alfresco/repo/avm/LayeredDirectoryNode.java b/source/java/org/alfresco/repo/avm/LayeredDirectoryNode.java index fdbd1e4c84..2f1b0a438c 100644 --- a/source/java/org/alfresco/repo/avm/LayeredDirectoryNode.java +++ b/source/java/org/alfresco/repo/avm/LayeredDirectoryNode.java @@ -1,489 +1,80 @@ -/* - * 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.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.TreeMap; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.avm.hibernate.BasicAttributesBean; -import org.alfresco.repo.avm.hibernate.BasicAttributesBeanImpl; -import org.alfresco.repo.avm.hibernate.DirectoryEntry; -import org.alfresco.repo.avm.hibernate.LayeredDirectoryNodeBean; -import org.alfresco.repo.avm.hibernate.LayeredDirectoryNodeBeanImpl; +import java.util.List; /** - * A layered directory node. A layered directory node points at - * an underlying directory, which may or may not exist. The visible - * contents of a layered directory node is the contents of the underlying node - * pointed at plus those nodes added to or modified in the layered directory node minus - * those nodes which have been deleted in the layered directory node. + * Interface for Layered Directories. * @author britt */ -public class LayeredDirectoryNode extends DirectoryNode implements Layered +interface LayeredDirectoryNode extends DirectoryNode, Layered { - /** - * The underlying bean data. - */ - private LayeredDirectoryNodeBean fData; - - /** - * Make one up from Bean data. - * @param data The bean with the persistent data. - */ - public LayeredDirectoryNode(LayeredDirectoryNodeBean data) - { - fData = data; - setDataBean(data); - } - - /** - * Make a new one from a specified indirection path. - * @param indirection The indirection path to set. - * @param repository The repository that owns this node. - */ - public LayeredDirectoryNode(String indirection, Repository repos) - { - // Set up basic attributes for this node. - long time = System.currentTimeMillis(); - // TODO We'll fix this up when Britt understands user management. - BasicAttributesBean attrs = new BasicAttributesBeanImpl("britt", - "britt", - "britt", - time, - time, - time); - fData = new LayeredDirectoryNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repos.getDataBean(), - attrs, - -1, - true, - indirection); - setDataBean(fData); - repos.getSuperRepository().getSession().save(fData); - } - - /** - * Kind of copy constructor, sort of. - * @param other The LayeredDirectoryNode we are copied from. - * @param repos The Repository object we use. - */ - public LayeredDirectoryNode(LayeredDirectoryNode other, - Repository repos) - { - LayeredDirectoryNodeBean thatBean = (LayeredDirectoryNodeBean)other.getDataBean(); - // Copy the basic attributes and update. - BasicAttributesBean attrs = new BasicAttributesBeanImpl(thatBean.getBasicAttributes()); - long time = System.currentTimeMillis(); - attrs.setCreateDate(time); - attrs.setModDate(time); - attrs.setAccessDate(time); - attrs.setLastModifier("britt"); - fData = new LayeredDirectoryNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repos.getDataBean(), - attrs, - -1, - thatBean.getPrimaryIndirection(), - other.getUnderlying()); - setDataBean(fData); - fData.setAdded(new HashMap(thatBean.getAdded())); - fData.setDeleted(new HashSet(thatBean.getDeleted())); - // fData.setPrimaryIndirection(thatBean.getPrimaryIndirection()); - repos.getSuperRepository().getSession().save(fData); - } - - /** - * Construct one from a PlainDirectoryNode. Called when a COW is performed in a layered - * context. - * @param other The PlainDirectoryNode. - * @param repos The Repository we should belong to. - * @param lPath The Lookup object. - */ - public LayeredDirectoryNode(PlainDirectoryNode other, - Repository repos, - Lookup lPath) - { - // TODO Fix this yada, yada. - BasicAttributesBean attrs = new BasicAttributesBeanImpl(other.getDataBean().getBasicAttributes()); - long time = System.currentTimeMillis(); - attrs.setModDate(time); - attrs.setAccessDate(time); - attrs.setLastModifier("britt"); - fData = new LayeredDirectoryNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repos.getDataBean(), - attrs, - -1, - false, - null); - setDataBean(fData); - // TODO Is this right? I don't think so. - // fData.setAdded(other.getListing(lPath, -1)); - // fData.setPrimaryIndirection(false); - repos.getSuperRepository().getSession().save(fData); - } - - /** - * Create a new layered directory based on a directory we are being named from - * that is in not in the layer of the source lookup. - * @param dir The directory - * @param repo The repository - * @param srcLookup The source lookup. - * @param name The name of the target. - */ - public LayeredDirectoryNode(DirectoryNode dir, - Repository repo, - Lookup srcLookup, - String name) - { - // Make BasicAttributes and set them correctly. - BasicAttributesBean attrs = new BasicAttributesBeanImpl(dir.getDataBean().getBasicAttributes()); - long time = System.currentTimeMillis(); - attrs.setCreateDate(time); - attrs.setModDate(time); - attrs.setAccessDate(time); - attrs.setCreator("britt"); - attrs.setLastModifier("britt"); - fData = new LayeredDirectoryNodeBeanImpl(repo.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repo.getDataBean(), - attrs, - -1, - true, - srcLookup.getIndirectionPath() + "/" + name); - setDataBean(fData); - repo.getSuperRepository().getSession().save(fData); - } - /** * Does this node have a primary indirection. * @returns Whether this is a primary indirection. */ - public boolean hasPrimaryIndirection() - { - return fData.getPrimaryIndirection(); - } - + public boolean getPrimaryIndirection(); + /** * Set whether this has a primary indirection. * @param has Whether this has a primary indirection. */ - public void setPrimaryIndirection(boolean has) - { - fData.setPrimaryIndirection(has); - } - - /** - * Get the raw underlying indirection. Only meaningful - * for a node that hasPrimaryIndirection(). - */ - public String getUnderlying() - { - return fData.getIndirection(); - } - - /** - * Get the underlying indirection in the context of a Lookup. - * @param lPath The lookup path. - */ - public String getUnderlying(Lookup lPath) - { - if (fData.getPrimaryIndirection()) - { - return fData.getIndirection(); - } - return lPath.getCurrentIndirection(); - } - + public void setPrimaryIndirection(boolean has); + /** * Get the layer id for this node. * @return The layer id. */ - public long getLayerID() - { - return fData.getLayerID(); - } - + public long getLayerID(); + /** * Set the layer id for this node. * @param layerID The id to set. */ - public void setLayerID(long id) - { - fData.setLayerID(id); - } - - /** - * Handle post copy on write details. - * @param parent - */ - public void handlePostCopy(DirectoryNode parent) - { - if (parent instanceof LayeredDirectoryNode) - { - LayeredDirectoryNode dir = (LayeredDirectoryNode)parent; - setLayerID(dir.getLayerID()); - } - } + public void setLayerID(long id); /** - * Copy on write logic. + * Set this to be a primary indirection from the path + * passed in. + * @param path The indirection path. + */ + public void rawSetPrimary(String path); + + /** + * Turn this node into a primary indirection node with the indirection + * taken from the Lookup passed in. + * Performs a copy on write. * @param lPath - * @return The copy or null. */ - public AVMNode possiblyCopy(Lookup lPath) - { - if (!shouldBeCopied()) - { - return null; - } - // Capture the repository. - Repository repo = lPath.getRepository(); - // Otherwise we do an actual copy. - LayeredDirectoryNode newMe = null; - long newBranchID = lPath.getHighestBranch(); - if (!lPath.isInThisLayer()) - { - if (hasPrimaryIndirection()) - { - newMe = new LayeredDirectoryNode(lPath.getIndirectionPath(), - repo); - } - else - { - newMe = new LayeredDirectoryNode((String)null, - repo); - newMe.setPrimaryIndirection(false); - } - } - else - { - newMe = new LayeredDirectoryNode(this, - repo); - - newMe.setLayerID(getLayerID()); - } - newMe.setAncestor(this); - newMe.setBranchID(newBranchID); - return newMe; - } + public void turnPrimary(Lookup lPath); - // TODO Start around here. + /** + * Retarget this directory. + * @param lPath The Lookup. + * @param target The new target path. + */ + public void retarget(Lookup lPath, String target); + + /** + * Make visible a node deleted in a layer. + * @param lPath The Lookup. + * @param name The name to make visible. + */ + public void uncover(Lookup lPath, String name); /** - * Insert a child node without COW. - * @param name The name to give the child. + * Set the indirection. + * @param indirection */ - public void putChild(String name, AVMNode node) - { - DirectoryEntry entry = new DirectoryEntry(node.getType(), node.getDataBean()); - fData.getAdded().put(name, entry); - fData.getDeleted().remove(name); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.DirectoryNode#addChild(java.lang.String, org.alfresco.repo.avm.AVMNode, org.alfresco.repo.avm.Lookup) - */ - public boolean addChild(String name, AVMNode child, Lookup lPath) - { - if (fData.getAdded().containsKey(name)) - { - return false; - } - if (!fData.getDeleted().contains(name)) - { - try - { - Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(-1, getUnderlying(lPath)); - DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); - if (dir.lookupChild(lookup, name, -1) != null) - { - return false; - } - } - catch (AlfrescoRuntimeException re) - { - // Do nothing. - } - } - DirectoryNode toModify = (DirectoryNode)copyOnWrite(lPath); - toModify.putChild(name, child); - child.setParent(toModify); - child.setRepository(lPath.getRepository()); - return true; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.DirectoryNode#directlyContains(org.alfresco.repo.avm.AVMNode) - */ - public boolean directlyContains(AVMNode node) - { - DirectoryEntry entry = new DirectoryEntry(node.getType(), - node.getDataBean()); - return fData.getAdded().containsValue(entry); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.DirectoryNode#getListing(org.alfresco.repo.avm.Lookup, int) - */ - public Map getListing(Lookup lPath, int version) - { - Map baseListing = null; - try - { - Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(version, getUnderlying(lPath)); - DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); - baseListing = dir.getListing(lookup, version); - } - catch (AlfrescoRuntimeException re) - { - baseListing = new HashMap(); - } - Map listing = new TreeMap(); - for (String name : baseListing.keySet()) - { - if (fData.getDeleted().contains(name)) - { - continue; - } - listing.put(name, baseListing.get(name)); - } - for (String name : fData.getAdded().keySet()) - { - listing.put(name, fData.getAdded().get(name)); - } - return listing; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.DirectoryNode#lookupChild(org.alfresco.repo.avm.Lookup, java.lang.String, int) - */ - public AVMNode lookupChild(Lookup lPath, String name, int version) - { - // TODO revisit the order in this. - if (fData.getAdded().containsKey(name)) - { - return AVMNodeFactory.CreateFromBean(fData.getAdded().get(name).getChild()); - } - AVMNode child = null; - try - { - Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(version, getUnderlying(lPath)); - DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); - child = dir.lookupChild(lookup, name, version); - } - catch (AlfrescoRuntimeException re) - { - return null; - } - if (child ==null) - { - return null; - } - if (fData.getDeleted().contains(name)) - { - return null; - } - return child; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.DirectoryNode#rawRemoveChild(java.lang.String) - */ - public void rawRemoveChild(String name) - { - fData.getAdded().remove(name); - fData.getDeleted().add(name); - } + public void setIndirection(String indirection); /** - * Needed for the slide operation. - * @param name The name of the child to remove. + * Get the indirection by another name. */ - public void rawRemoveChildNoGhost(String name) - { - fData.getAdded().remove(name); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.DirectoryNode#removeChild(java.lang.String, org.alfresco.repo.avm.Lookup) + public String getUnderlying(); + + /** + * Get all deleted children for this directory. + * @return All deleted children. */ - public boolean removeChild(String name, Lookup lPath) - { - if (fData.getDeleted().contains(name)) - { - return false; - } - if (!fData.getAdded().containsKey(name)) - { - try - { - Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(-1, getUnderlying(lPath)); - DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); - if (dir.lookupChild(lookup, name, -1) == null) - { - return false; - } - } - catch (AlfrescoRuntimeException re) - { - return false; - } - } - LayeredDirectoryNode toModify = - (LayeredDirectoryNode)copyOnWrite(lPath); - toModify.rawRemoveChild(name); - return true; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMNode#getType() - */ - public int getType() - { - return AVMNodeType.LAYERED_DIRECTORY; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - public String toString(Lookup lPath) - { - return "[LD:" + fData.getId() + ":" + getUnderlying(lPath) + "]"; - } -} + public List getDeleted(); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java b/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java new file mode 100644 index 0000000000..f48501f3ec --- /dev/null +++ b/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java @@ -0,0 +1,749 @@ +/* + * 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.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.hibernate.LockMode; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.proxy.HibernateProxy; + +/** + * A layered directory node. A layered directory node points at + * an underlying directory, which may or may not exist. The visible + * contents of a layered directory node is the contents of the underlying node + * pointed at plus those nodes added to or modified in the layered directory node minus + * those nodes which have been deleted in the layered directory node. + * @author britt + */ +class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirectoryNode +{ + static final long serialVersionUID = 4623043057918181724L; + + /** + * The layer id. + */ + private long fLayerID; + + /** + * The pointer to the underlying directory. + */ + private String fIndirection; + + /** + * Whether this is a primary indirection node. + */ + private boolean fPrimaryIndirection; + + /** + * Default constructor. Called by Hibernate. + */ + protected LayeredDirectoryNodeImpl() + { + } + + /** + * Make a new one from a specified indirection path. + * @param indirection The indirection path to set. + * @param repository The repository that owns this node. + */ + public LayeredDirectoryNodeImpl(String indirection, Repository repos) + { + super(repos.getSuperRepository().issueID(), repos); + fLayerID = -1; + fIndirection = indirection; + fPrimaryIndirection = true; + repos.getSuperRepository().getSession().save(this); + } + + /** + * Kind of copy constructor, sort of. + * @param other The LayeredDirectoryNode we are copied from. + * @param repos The Repository object we use. + */ + @SuppressWarnings("unchecked") + public LayeredDirectoryNodeImpl(LayeredDirectoryNode other, + Repository repos) + { + super(repos.getSuperRepository().issueID(), repos); + Session sess = repos.getSuperRepository().getSession(); + fIndirection = other.getUnderlying(); + fPrimaryIndirection = other.getPrimaryIndirection(); + fLayerID = -1; + sess.save(this); + sess.flush(); + for (ChildEntry child : other.getChildren()) + { + ChildEntryImpl newChild = new ChildEntryImpl(child.getName(), + this, + child.getChild()); + repos.getSuperRepository().getSession().save(newChild); + } + for (DeletedChild dc : other.getDeleted()) + { + DeletedChild newDel = new DeletedChildImpl(dc.getName(), + this); + sess.save(newDel); + } + } + + /** + * Construct one from a PlainDirectoryNode. Called when a COW is performed in a layered + * context. + * @param other The PlainDirectoryNode. + * @param repos The Repository we should belong to. + * @param lPath The Lookup object. + */ + @SuppressWarnings("unchecked") + public LayeredDirectoryNodeImpl(PlainDirectoryNode other, + Repository repos, + Lookup lPath, + boolean copyContents) + { + super(repos.getSuperRepository().issueID(), repos); + fIndirection = null; + fPrimaryIndirection = false; + fLayerID = -1; + Session sess = repos.getSuperRepository().getSession(); + sess.save(this); + if (copyContents) + { + sess.flush(); + for (ChildEntry child : other.getChildren()) + { + ChildEntryImpl newChild = new ChildEntryImpl(child.getName(), + this, + child.getChild()); + sess.save(newChild); + } + } + } + + /** + * Create a new layered directory based on a directory we are being named from + * that is in not in the layer of the source lookup. + * @param dir The directory + * @param repo The repository + * @param srcLookup The source lookup. + * @param name The name of the target. + */ + public LayeredDirectoryNodeImpl(DirectoryNode dir, + Repository repo, + Lookup srcLookup, + String name) + { + super(repo.getSuperRepository().issueID(), repo); + fIndirection = srcLookup.getIndirectionPath() + "/" + name; + fPrimaryIndirection = true; + fLayerID = -1; + repo.getSuperRepository().getSession().save(this); + } + + /** + * Is this a primary indirection node. + * @return Whether this is a primary indirection. + */ + public boolean getPrimaryIndirection() + { + return fPrimaryIndirection; + } + + /** + * Set the primary indirection state of this. + * @param has Whether this is a primary indirection node. + */ + public void setPrimaryIndirection(boolean has) + { + fPrimaryIndirection = has; + } + + /** + * Get the indirection path. + * @return The indirection path. + */ + public String getIndirection() + { + return fIndirection; + } + + public String getUnderlying() + { + return fIndirection; + } + + /** + * Get the underlying path in the Lookup's context. + * @param lPath The Lookup. + * @return The underlying path. + */ + public String getUnderlying(Lookup lPath) + { + if (fPrimaryIndirection) + { + return fIndirection; + } + return lPath.getCurrentIndirection(); + } + + /** + * Get the layer id. + * @return The layer id. + */ + public long getLayerID() + { + return fLayerID; + } + + /** + * Set the layer id. + * @param id The id to set. + */ + public void setLayerID(long id) + { + fLayerID = id; + } + + /** + * Copy on write logic. + * @param lPath + * @return The copy or null. + */ + public AVMNodeImpl possiblyCopy(Lookup lPath) + { + if (!lPath.needsCopying()) + { + return null; + } + // Capture the repository. + Repository repo = lPath.getRepository(); + // Otherwise we do an actual copy. + LayeredDirectoryNodeImpl newMe = null; + if (!lPath.isInThisLayer()) + { + // This means that this is being seen indirectly through the topmost + // layer. The following creates a node that will inherit its + // indirection from its parent. + newMe = new LayeredDirectoryNodeImpl((String)null, + repo); + newMe.setPrimaryIndirection(false); + newMe.setLayerID(lPath.getTopLayer().getLayerID()); + } + else + { + // A simple copy is made. + newMe = new LayeredDirectoryNodeImpl(this, + repo); + newMe.setLayerID(getLayerID()); + } + newMe.setAncestor(this); + return newMe; + } + + /** + * Insert a child node without COW. + * @param name The name to give the child. + */ + public void putChild(String name, AVMNode node) + { + Session sess = SuperRepository.GetInstance().getSession(); +// sess.lock(this, LockMode.UPGRADE); + ChildEntry entry = new ChildEntryImpl(name, this, node); + ChildEntry existing = (ChildEntry)sess.get(ChildEntryImpl.class, (Serializable)entry); + if (existing != null) + { + existing.setChild(node); + } + else + { + sess.save(entry); + } + DeletedChild dc = getDeleted("name"); + if (dc != null) + { + sess.delete(dc); + } + } + + /** + * Add a child to this directory and possibly COW. + * @param name The name of the child to add. + * @param child The child to add. + * @param lPath The Lookup. + * @return Whether the child was successfully added. + */ + public boolean addChild(String name, AVMNode child, Lookup lPath) + { + if (getChild(name) != null) + { + return false; + } + if (getDeleted(name) == null) + { + try + { + Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(-1, getUnderlying(lPath)); + DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); + if (dir.lookupChild(lookup, name, -1) != null) + { + return false; + } + } + catch (AVMException re) + { + if (re instanceof AVMCycleException) + { + throw re; + } + // Do nothing. + } + } + DirectoryNode toModify = (DirectoryNode)copyOnWrite(lPath); + toModify.putChild(name, child); + child.setParent(toModify); + child.setRepository(lPath.getRepository()); + return true; + } + + /** + * Does this node directly contain the indicated node. + * @param node The node we are checking. + * @return Whether node is directly contained. + */ + public boolean directlyContains(AVMNode node) + { + return getChild(node) != null; + } + + /** + * Get a listing of the virtual contents of this directory. + * @param lPath The Lookup. + * @return A Map from names to nodes. This is a sorted Map. + */ + @SuppressWarnings("unchecked") + public Map getListing(Lookup lPath) + { + // Get the base listing from the thing we indirect to. + Map baseListing = null; + try + { + Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(-1, getUnderlying(lPath)); + DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); + baseListing = dir.getListing(lookup); + } + catch (AVMException re) + { + if (re instanceof AVMCycleException) + { + throw re; + } + // It's OK for an indirection to dangle. + baseListing = new HashMap(); + } + // Filter the base listing by taking out anything in the deleted Set. + Map listing = new TreeMap(); + for (String name : baseListing.keySet()) + { + if (getDeleted(name) != null) + { + continue; + } + listing.put(name, baseListing.get(name)); + } + for (ChildEntry entry : getChildren()) + { + listing.put(entry.getName(), entry.getChild()); + } + return listing; + } + + /** + * Get a listing from a directory node descriptor. + * @param dir The directory node descriptor. + * @return A Map of names to node descriptors. + */ + public Map getListing(AVMNodeDescriptor dir) + { + if (dir.getPath() == null || dir.getIndirection() == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + Map baseListing = new TreeMap(); + try + { + Lookup lookup = SuperRepository.GetInstance().lookupDirectory(-1, dir.getIndirection()); + DirectoryNode dirNode = (DirectoryNode)lookup.getCurrentNode(); + Map listing = dirNode.getListing(lookup); + for (String name : listing.keySet()) + { + baseListing.put(name, + listing.get(name).getDescriptor(dir.getPath(), name, + lookup.getCurrentIndirection())); + } + } + catch (AVMException e) + { + if (e instanceof AVMCycleException) + { + throw e; + } + } + List deleted = getDeleted(); + for (DeletedChild child : deleted) + { + baseListing.remove(child.getName()); + } + List children = getChildren(); + for (ChildEntry child : children) + { + baseListing.put(child.getName(), + child.getChild().getDescriptor(dir.getPath(), + child.getName(), + dir.getIndirection())); + } + return baseListing; + } + + /** + * Lookup a child by name. + * @param lPath The Lookup. + * @param name The name we are looking. + * @param version The version in which we are looking. + * @return The child or null if not found. + */ + @SuppressWarnings("unchecked") + public AVMNode lookupChild(Lookup lPath, String name, int version) + { + // If the name has been deleted quickly return. + if (getDeleted(name) != null) + { + return null; + } + ChildEntry entry = getChild(name); + if (entry != null) + { + AVMNode child = entry.getChild(); + if (child instanceof HibernateProxy) + { + HibernateProxy proxy = (HibernateProxy)child; + return (AVMNode)proxy.getHibernateLazyInitializer().getImplementation(); + } + return child; + } + // Not here so check our indirection. + try + { + Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(-1, getUnderlying(lPath)); + DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); + return dir.lookupChild(lookup, name, -1); + } + catch (AVMException re) + { + if (re instanceof AVMCycleException) + { + throw re; + } + return null; + } + } + + /** + * Lookup a child using a node descriptor as context. + * @param mine The node descriptor for this, + * @param name The name to lookup, + * @return The node descriptor. + */ + public AVMNodeDescriptor lookupChild(AVMNodeDescriptor mine, String name) + { + if (mine.getPath() == null || mine.getIndirection() == null) + { + throw new AVMBadArgumentException("Illegal null argument."); + } + if (getDeleted(name) != null) + { + return null; + } + ChildEntry entry = getChild(name); + if (entry != null) + { + return entry.getChild().getDescriptor(mine.getPath(), + name, + mine.getIndirection()); + } + try + { + Lookup lookup = SuperRepository.GetInstance().lookupDirectory(-1, mine.getIndirection()); + DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); + AVMNode child = dir.lookupChild(lookup, name, -1); + if (child == null) + { + return null; + } + return child.getDescriptor(lookup); + } + catch (AVMException e) + { + if (e instanceof AVMCycleException) + { + throw e; + } + return null; + } + } + + /** + * Directly remove a child. Do not COW. Do not pass go etc. + * @param name The name of the child to remove. + */ + @SuppressWarnings("unchecked") + public void rawRemoveChild(String name) + { + ChildEntry entry = getChild(name); + if (entry != null) + { + SuperRepository.GetInstance().getSession().delete(entry); + } + DeletedChild dc = new DeletedChildImpl(name, + this); + SuperRepository.GetInstance().getSession().save(dc); + } + + /** + * Remove a child by name. Possibly COW. + * @param name The name of the child to remove. + * @param lPath The Lookup. + * @return Whether the child was successfully removed. + */ + @SuppressWarnings("unchecked") + public boolean removeChild(String name, Lookup lPath) + { + // Can't delete something that is already deleted. + if (getDeleted(name) != null) + { + return false; + } + ChildEntry entry = getChild(name); + if (entry == null) + { + // See if the name is seen via indirection. + try + { + Lookup lookup = lPath.getRepository().getSuperRepository().lookupDirectory(-1, getUnderlying(lPath)); + DirectoryNode dir = (DirectoryNode)lookup.getCurrentNode(); + if (dir.lookupChild(lookup, name, -1) == null) + { + return false; + } + } + catch (AVMException re) + { + if (re instanceof AVMCycleException) + { + throw re; + } + return false; + } + } + LayeredDirectoryNode toModify = + (LayeredDirectoryNode)copyOnWrite(lPath); + toModify.rawRemoveChild(name); + return true; + } + + /** + * Get the type of this node. + * @return The type of this node. + */ + public int getType() + { + return AVMNodeType.LAYERED_DIRECTORY; + } + + /** + * For diagnostics. Get a String representation. + * @param lPath The Lookup. + * @return A String representation. + */ + public String toString(Lookup lPath) + { + return "[LD:" + getId() + ":" + getUnderlying(lPath) + "]"; + } + + /** + * Set the primary indirection. No COW. + * @param path The indirection path. + */ + public void rawSetPrimary(String path) + { + fIndirection = path; + fPrimaryIndirection = true; + } + + /** + * Make this node become a primary indirection. COW. + * @param lPath The Lookup. + */ + public void turnPrimary(Lookup lPath) + { + String path = lPath.getCurrentIndirection(); + LayeredDirectoryNode toModify = (LayeredDirectoryNode)copyOnWrite(lPath); + toModify.rawSetPrimary(path); + } + + /** + * Make this point at a new target. + * @param lPath The Lookup. + */ + public void retarget(Lookup lPath, String target) + { + LayeredDirectoryNode toModify = (LayeredDirectoryNode)copyOnWrite(lPath); + toModify.rawSetPrimary(target); + } + + /** + * Let anything behind name in this become visible. + * @param lPath The Lookup. + * @param name The name to uncover. + */ + public void uncover(Lookup lPath, String name) + { + LayeredDirectoryNodeImpl toModify = (LayeredDirectoryNodeImpl)copyOnWrite(lPath); + DeletedChild dc = toModify.getDeleted(name); + if (dc != null) + { + lPath.getRepository().getSuperRepository().getSession().delete(dc); + } + } + + /** + * Get the descriptor for this node. + * @param The Lookup. + * @return A descriptor. + */ + public AVMNodeDescriptor getDescriptor(Lookup lPath) + { + BasicAttributes attrs = getBasicAttributes(); + return new AVMNodeDescriptor(lPath.getRepresentedPath(), + AVMNodeType.LAYERED_DIRECTORY, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + getUnderlying(lPath), + fPrimaryIndirection, + fLayerID, + -1); + } + + /** + * Get a descriptor for this. + * @param parentPath The parent path. + * @param name The name this was looked up with. + * @param parentIndirection The indirection of the parent. + * @return + */ + public AVMNodeDescriptor getDescriptor(String parentPath, String name, String parentIndirection) + { + BasicAttributes attrs = getBasicAttributes(); + String path = parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name; + String indirection = null; + if (fPrimaryIndirection) + { + indirection = fIndirection; + } + else + { + indirection = parentIndirection.endsWith("/") ? parentIndirection + name : + parentIndirection + "/" + name; + } + return new AVMNodeDescriptor(path, + AVMNodeType.LAYERED_DIRECTORY, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + indirection, + fPrimaryIndirection, + fLayerID, + -1); + } + + /** + * Set the indirection. + * @param indirection + */ + public void setIndirection(String indirection) + { + fIndirection = indirection; + } + + /** + * Get the deleted child entry for a given name. + * @param name The name to look for. + * @return A DeletedChild object. + */ + @SuppressWarnings("unchecked") + private DeletedChild getDeleted(String name) + { + Query query = SuperRepository.GetInstance().getSession().getNamedQuery("DeletedChild.ByNameParent"); + query.setString("name", name); + query.setEntity("parent", this); + query.setCacheable(true); + query.setCacheRegion("DeletedChild.ByNameParent"); + List dc = (List)query.list(); + if (dc.size() == 0) + { + return null; + } + return dc.get(0); + } + + /** + * Get all the deleted entries in this directory. + * @return A List of DeletedEntry objects. + */ + @SuppressWarnings("unchecked") + public List getDeleted() + { + Query query = SuperRepository.GetInstance().getSession().getNamedQuery("DeletedChild.ByParent"); + query.setEntity("parent", this); + query.setCacheable(true); + query.setCacheRegion("DeletedChild.ByParent"); + return (List)query.list(); + } + + /** + * Does nothing because LayeredDirectoryNodes can't be roots. + * @param isRoot + */ + public void setIsRoot(boolean isRoot) + { + } +} diff --git a/source/java/org/alfresco/repo/avm/LayeredFileNode.java b/source/java/org/alfresco/repo/avm/LayeredFileNode.java index c7f6c05461..35918f3d58 100644 --- a/source/java/org/alfresco/repo/avm/LayeredFileNode.java +++ b/source/java/org/alfresco/repo/avm/LayeredFileNode.java @@ -14,204 +14,12 @@ * language governing permissions and limitations under the * License. */ - package org.alfresco.repo.avm; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.avm.hibernate.BasicAttributesBean; -import org.alfresco.repo.avm.hibernate.BasicAttributesBeanImpl; -import org.alfresco.repo.avm.hibernate.LayeredFileNodeBean; -import org.alfresco.repo.avm.hibernate.LayeredFileNodeBeanImpl; - /** - * A LayeredFileNode behaves like a copy on write symlink. + * Interface for a layered file node. * @author britt */ -public class LayeredFileNode extends FileNode implements Layered +interface LayeredFileNode extends FileNode, Layered { - /** - * The data bean. - */ - private LayeredFileNodeBean fData; - - /** - * Construct one from its data bean. - */ - public LayeredFileNode(LayeredFileNodeBean data) - { - fData = data; - setDataBean(fData); - } - - // TODO Is this ever used? - /** - * Basically a copy constructor. - * @param other The file to make a copy of. - * @param repo The repository that contains us. - */ - public LayeredFileNode(LayeredFileNode other, Repository repo) - { - long time = System.currentTimeMillis(); - BasicAttributesBean attrs = - new BasicAttributesBeanImpl(other.getDataBean().getBasicAttributes()); - attrs.setCreateDate(time); - attrs.setModDate(time); - attrs.setAccessDate(time); - attrs.setCreator("britt"); - attrs.setLastModifier("britt"); - fData = - new LayeredFileNodeBeanImpl(repo.getSuperRepository().issueID(), - -1, - 0L, - null, - null, - null, - repo.getDataBean(), - attrs, - other.fData.getIndirection()); - repo.getSuperRepository().getSession().save(fData); - setDataBean(fData); - } - - // TODO I'm not at all sure that these are the right semantics. - /** - * Create a new one in a layered context, when it is the result of - * a renaming. - * @param file The node we are being made from. - * @param repos The Repository. - * @param srcLookup The lookup for the source parent directory. We - * need this to get calculate the correct indirection information. - * @param name The name - */ - public LayeredFileNode(FileNode file, - Repository repos, - Lookup srcLookup, - String name) - { - long time = System.currentTimeMillis(); - BasicAttributesBean attrs = - new BasicAttributesBeanImpl(file.getDataBean().getBasicAttributes()); - attrs.setCreateDate(time); - attrs.setModDate(time); - attrs.setAccessDate(time); - attrs.setCreator("britt"); - attrs.setLastModifier("britt"); - fData = - new LayeredFileNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0L, - null, - null, - null, - repos.getDataBean(), - attrs, - srcLookup.getIndirectionPath() + "/" + name); - repos.getSuperRepository().getSession().save(fData); - setDataBean(fData); - } - - /** - * Make a brand new layered file node. - * @param indirection The thing we point to. - * @param repo The repository we belong to. - */ - public LayeredFileNode(String indirection, Repository repo) - { - long time = System.currentTimeMillis(); - BasicAttributesBean attrs = new BasicAttributesBeanImpl("britt", - "britt", - "britt", - time, - time, - time); - fData = new LayeredFileNodeBeanImpl(repo.getSuperRepository().issueID(), - -1, - 0L, - null, - null, - null, - repo.getDataBean(), - attrs, - indirection); - repo.getSuperRepository().getSession().save(fData); - setDataBean(fData); - } - - /** - * Set the repository after a copy. - * @param parent The parent after copying. - */ - public void handlePostCopy(DirectoryNode parent) - { - } - - /** - * Copy on write logic. - * @param lPath The path by which this was found. - */ - public AVMNode possiblyCopy(Lookup lPath) - { - Lookup lookup = lPath.getRepository().getSuperRepository().lookup(-1, fData.getIndirection()); - AVMNode indirect = lookup.getCurrentNode(); - if (!(indirect instanceof FileNode)) - { - throw new AlfrescoRuntimeException("Unbacked layered file node."); - } - // This is a mildly dirty trick. We use getContentForRead so as not to startle - // the ultimate destination content into copying itself prematurely. - FileContent content = ((FileNode)indirect).getContentForRead(-1, lPath.getRepository()); - PlainFileNode newMe = new PlainFileNode(content, lPath.getRepository(), fData.getBasicAttributes()); - newMe.setAncestor(this); - return newMe; - } - - /** - * Get the type of this node. - * @return The type. - */ - public int getType() - { - return AVMNodeType.LAYERED_FILE; - } - - /** - * Get the content of the specified version. - * @return A FileContent object. - */ - public FileContent getContentForRead(int version, Repository repo) - { - Lookup lookup = repo.getSuperRepository().lookup(version, fData.getIndirection()); - AVMNode node = lookup.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AlfrescoRuntimeException("Missing Link."); - } - FileNode file = (FileNode)node; - return file.getContentForRead(version, repo); - } - - /** - * Get File Content for writing. Should never be called. - */ - public FileContent getContentForWrite(Repository repo) - { - assert false : "Never happens"; - return null; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Layered#getUnderlying(org.alfresco.repo.avm.Lookup) - */ - public String getUnderlying(Lookup lookup) - { - return fData.getIndirection(); - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - public String toString(Lookup lPath) - { - return "[LF:" + fData.getId() + ":" + fData.getIndirection() + "]"; - } } diff --git a/source/java/org/alfresco/repo/avm/LayeredFileNodeImpl.java b/source/java/org/alfresco/repo/avm/LayeredFileNodeImpl.java new file mode 100644 index 0000000000..7b102bf6cc --- /dev/null +++ b/source/java/org/alfresco/repo/avm/LayeredFileNodeImpl.java @@ -0,0 +1,213 @@ +/* + * 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; + +/** + * A LayeredFileNode behaves like a copy on write symlink. + * @author britt + */ +class LayeredFileNodeImpl extends FileNodeImpl implements LayeredFileNode +{ + static final long serialVersionUID = 9208423010479156363L; + + /** + * The indirection. + */ + private String fIndirection; + + /** + * Anonymous constructor. + */ + protected LayeredFileNodeImpl() + { + } + + /** + * Basically a copy constructor. Used when a branch is created + * from a layered file. + * @param other The file to make a copy of. + * @param repo The repository that contains us. + */ + public LayeredFileNodeImpl(LayeredFileNode other, Repository repo) + { + super(repo.getSuperRepository().issueID(), repo); + fIndirection = other.getIndirection(); + repo.getSuperRepository().getSession().save(this); + } + + /** + * Make a brand new layered file node. + * @param indirection The thing we point to. + * @param repo The repository we belong to. + */ + public LayeredFileNodeImpl(String indirection, Repository repo) + { + super(repo.getSuperRepository().issueID(), repo); + fIndirection = indirection; + repo.getSuperRepository().getSession().save(this); + } + + /** + * Copy on write logic. + * @param lPath The path by which this was found. + */ + public AVMNode possiblyCopy(Lookup lPath) + { + // LayeredFileNodes are always copied. + Lookup lookup = lPath.getRepository().getSuperRepository().lookup(-1, fIndirection); + AVMNode indirect = lookup.getCurrentNode(); + if (indirect.getType() != AVMNodeType.LAYERED_FILE && + indirect.getType() != AVMNodeType.PLAIN_FILE) + { + throw new AVMException("Unbacked layered file node."); + } + // This is a mildly dirty trick. We use getContentForRead so as not to startle + // the ultimate destination content into copying itself prematurely. + FileContent content = ((FileNode)indirect).getContentForRead(lPath.getRepository()); + PlainFileNodeImpl newMe = new PlainFileNodeImpl(content, lPath.getRepository(), getBasicAttributes()); + newMe.setAncestor(this); + return newMe; + } + + /** + * Get the type of this node. + * @return The type. + */ + public int getType() + { + return AVMNodeType.LAYERED_FILE; + } + + /** + * Get the content of the specified version. + * @param repo The Repository. + * @return A FileContent object. + */ + public FileContent getContentForRead(Repository repo) + { + Lookup lookup = repo.getSuperRepository().lookup(-1, fIndirection); + AVMNode node = lookup.getCurrentNode(); + if (node.getType() != AVMNodeType.LAYERED_FILE && + node.getType() != AVMNodeType.PLAIN_FILE) + { + throw new AVMException("Missing Link."); + } + FileNode file = (FileNode)node; + return file.getContentForRead(repo); + } + + /** + * Get File Content for writing. Should never be called. + * @param repo The Repository. + * @return Always null. + */ + public FileContent getContentForWrite(Repository repo) + { + assert false : "Never happens"; + return null; + } + + /** + * Get the underlying path. + * @param lookup The Lookup. (Unused here.) + * @return The underlying path. + */ + public String getUnderlying(Lookup lookup) + { + return fIndirection; + } + + /** + * Get a diagnostic String representation. + * @param lPath The Lookup. + * @return A diagnostic String representation. + */ + public String toString(Lookup lPath) + { + return "[LF:" + getId() + ":" + fIndirection + "]"; + } + + /** + * Get the descriptor for this node. + * @param The Lookup. + * @return A descriptor. + */ + public AVMNodeDescriptor getDescriptor(Lookup lPath) + { + BasicAttributes attrs = getBasicAttributes(); + return new AVMNodeDescriptor(lPath.getRepresentedPath(), + AVMNodeType.LAYERED_FILE, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + getUnderlying(lPath), + false, + -1, + 0); + } + + /** + * Get the descriptor for this node. + * @param parentPath The parent path. + * @param name The name this was looked up with. + * @param parentIndirection The parent indirection. + * @return + */ + public AVMNodeDescriptor getDescriptor(String parentPath, String name, String parentIndirection) + { + BasicAttributes attrs = getBasicAttributes(); + String path = parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name; + return new AVMNodeDescriptor(path, + AVMNodeType.LAYERED_FILE, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + fIndirection, + false, + -1, + 0); + } + + /** + * Get the indirection. + * @return The indirection. + */ + public String getIndirection() + { + return fIndirection; + } + + /** + * Set the indirection. + * @param indirection + */ + public void setIndirection(String indirection) + { + fIndirection = indirection; + } +} diff --git a/source/java/org/alfresco/repo/avm/Lookup.java b/source/java/org/alfresco/repo/avm/Lookup.java index 95e2dc9a5f..428f3788b1 100644 --- a/source/java/org/alfresco/repo/avm/Lookup.java +++ b/source/java/org/alfresco/repo/avm/Lookup.java @@ -20,22 +20,25 @@ package org.alfresco.repo.avm; import java.util.ArrayList; import java.util.List; +import org.hibernate.LockMode; +import org.hibernate.Session; + /** * This holds all the information necessary to perform operations - * on AVMNodes. + * on AVMNodes, and is internall structured as a list of path components + * from the root directory of a repository. * @author britt */ -public class Lookup +class Lookup { /** * The Repository. */ - private Repository fRepository; + private RepositoryImpl fRepository; /** * The name of the Repository. */ - @SuppressWarnings("unused") private String fRepName; /** @@ -59,11 +62,6 @@ public class Lookup */ private int fTopLayerIndex; - /** - * The highest branch id seen in the lookup. - */ - private long fHighestBranchID; - /** * The lowest layered directory node's index seen so far. */ @@ -74,22 +72,27 @@ public class Lookup */ private int fPosition; + /** + * Whether a needs-to-be-copied component has been seen. + */ + private boolean fNeedsCopying; + /** * Create a new one. * @param repository The Repository that's being looked in. * @param repName The name of that Repsository. */ - public Lookup(Repository repository, String repName) + public Lookup(RepositoryImpl repository, String repName) { fRepository = repository; fRepName = repName; fComponents = new ArrayList(); fLayeredYet = false; fTopLayer = null; - fHighestBranchID = 0; fPosition = -1; fTopLayerIndex = -1; fLowestLayerIndex = -1; + fNeedsCopying = false; } /** @@ -102,26 +105,37 @@ public class Lookup LookupComponent comp = new LookupComponent(); comp.setName(name); comp.setNode(node); - // Bump up the highest branch id seen if necessary. - if (node.getBranchID() > fHighestBranchID) +// SuperRepository.GetInstance().getSession().lock(node, LockMode.READ); + if (!node.getIsNew()) { - fHighestBranchID = node.getBranchID(); + fNeedsCopying = true; } - // Set the highest branch id seen by this component in the lookup. - comp.setHighestBranch(fHighestBranchID); + else + { + if (fPosition >= 0 && (!((DirectoryNode)fComponents.get(fPosition).getNode()).directlyContains(node) || + !isDirectlyContained())) + { + fNeedsCopying = true; + } + } + comp.setNeedsCopy(fNeedsCopying); // Record various things if this is layered. - if (node instanceof LayeredDirectoryNode) + if (node.getType() == AVMNodeType.LAYERED_DIRECTORY) { LayeredDirectoryNode oNode = (LayeredDirectoryNode)node; // Record the indirection path that should be used. - if (oNode.hasPrimaryIndirection()) + if (oNode.getPrimaryIndirection()) { comp.setIndirection(oNode.getUnderlying()); } else { String parentIndirection = fComponents.get(fPosition).getIndirection(); - if (parentIndirection.endsWith("/")) // TODO This currently is impossible because + if (parentIndirection == null) + { + System.out.println("Oink!"); + } + if (parentIndirection.endsWith("/")) // This currently is impossible because // root dirs are always plain. { comp.setIndirection(parentIndirection + name); @@ -173,6 +187,28 @@ public class Lookup assert fPosition >= 0; return fComponents.get(fPosition).isLayered(); } + + /** + * Determine if a node is directly contained. + */ + private boolean isDirectlyContained() + { + if (!isLayered()) + { + return true; + } + int pos = fPosition; + while (pos > 1) + { + DirectoryNode dir = (DirectoryNode)fComponents.get(pos - 1).getNode(); + if (!dir.directlyContains(fComponents.get(pos).getNode())) + { + return false; + } + pos--; + } + return true; + } /** * Determine if a node is directly in this layer. @@ -199,7 +235,7 @@ public class Lookup { return false; } - if (dir == fTopLayer) + if (dir.equals(fTopLayer)) { return true; } @@ -208,15 +244,6 @@ public class Lookup return false; } - /** - * Get the highest branch traversed in this lookup at the current position. - * @return The highest branch traversed. - */ - public long getHighestBranch() - { - return fComponents.get(fPosition).getHighestBranch(); - } - /** * Get the name of the current component. * @return The name. @@ -256,14 +283,14 @@ public class Lookup for (int pos = lowestLayerIndex; pos >= fTopLayerIndex; pos--) { AVMNode node = fComponents.get(pos).getNode(); - if (!(node instanceof LayeredDirectoryNode)) + if (node.getType() != AVMNodeType.LAYERED_DIRECTORY) { continue; } LayeredDirectoryNode oNode = (LayeredDirectoryNode)node; if (oNode.getLayerID() == fTopLayer.getLayerID() && - oNode.hasPrimaryIndirection()) + oNode.getPrimaryIndirection()) { StringBuilder builder = new StringBuilder(); builder.append(oNode.getUnderlying()); @@ -308,4 +335,47 @@ public class Lookup { return fRepository; } + + /** + * Get the path represented by this lookup. + * @return The canonical path for this lookup. + */ + public String getRepresentedPath() + { + if (fComponents.size() == 1) + { + return fRepName + ":/"; + } + StringBuilder builder = new StringBuilder(); + builder.append(fRepName); + builder.append(':'); + int count = fComponents.size(); + for (int i = 1; i < count; i++) + { + builder.append('/'); + builder.append(fComponents.get(i).getName()); + } + return builder.toString(); + } + + /** + * Get whether the current node needs copying. + * @return Whether the current node needs copying. + */ + public boolean needsCopying() + { + return fComponents.get(fPosition).getNeedsCopy(); + } + + /** + * Acquire locks for writing, in path lookup order. + */ +// public void acquireLocks() +// { +// Session sess = SuperRepository.GetInstance().getSession(); +// for (LookupComponent comp : fComponents) +// { +// sess.lock(comp.getNode(), LockMode.UPGRADE); +// } +// } } diff --git a/source/java/org/alfresco/repo/avm/LookupComponent.java b/source/java/org/alfresco/repo/avm/LookupComponent.java index 8cff08efeb..9aa4753a9f 100644 --- a/source/java/org/alfresco/repo/avm/LookupComponent.java +++ b/source/java/org/alfresco/repo/avm/LookupComponent.java @@ -21,7 +21,7 @@ package org.alfresco.repo.avm; * Represents a path component in a lookup. * @author britt */ -public class LookupComponent +class LookupComponent { /** * The name of this component. @@ -33,11 +33,6 @@ public class LookupComponent */ private AVMNode fNode; - /** - * The highest branch seen by this component. - */ - private long fHighestBranch; - /** * The indirection path (if any) for this node. */ @@ -52,14 +47,11 @@ public class LookupComponent * Whether this node is in a layer. */ private boolean fLayered; - + /** - * @return the highestBranch + * Whether this needs copying. */ - public long getHighestBranch() - { - return fHighestBranch; - } + private boolean fNeedsCopy; /** * Create a new empty lookup component. @@ -69,14 +61,7 @@ public class LookupComponent } /** - * @param highestBranch the highestBranch to set - */ - public void setHighestBranch(long highestBranch) - { - fHighestBranch = highestBranch; - } - - /** + * Get the indirection. * @return the indirection */ public String getIndirection() @@ -85,6 +70,7 @@ public class LookupComponent } /** + * Set the indirection. * @param indirection the indirection to set */ public void setIndirection(String indirection) @@ -93,7 +79,9 @@ public class LookupComponent } /** - * @return the layered + * Is this component layered. I.e. has it seen a layer yet in + * its lookup. + * @return Whether this component is layered. */ public boolean isLayered() { @@ -101,7 +89,8 @@ public class LookupComponent } /** - * @param layered the layered to set + * Set whether this node is layered. + * @param layered */ public void setLayered(boolean layered) { @@ -109,6 +98,8 @@ public class LookupComponent } /** + * Get the index of the lowest (in the path lookup sense) layer + * seen at this component's point in the lookup. * @return the lowestLayerIndex */ public int getLowestLayerIndex() @@ -117,6 +108,8 @@ public class LookupComponent } /** + * Set the index of the lowest (in the path lookup sense) layer + * seen at this components's point in the lookup. * @param lowestLayerIndex the lowestLayerIndex to set */ public void setLowestLayerIndex(int lowestLayerIndex) @@ -125,6 +118,7 @@ public class LookupComponent } /** + * Get the path component name. * @return the name */ public String getName() @@ -133,6 +127,7 @@ public class LookupComponent } /** + * Set the path component name. * @param name the name to set */ public void setName(String name) @@ -141,6 +136,7 @@ public class LookupComponent } /** + * Get the looked up node for this component. * @return the node */ public AVMNode getNode() @@ -149,10 +145,29 @@ public class LookupComponent } /** + * Set the node for this component. * @param node the node to set */ public void setNode(AVMNode node) { fNode = node; } + + /** + * Set the needs copy bit. + * @param needs Whether this component needs to be copied. + */ + public void setNeedsCopy(boolean needs) + { + fNeedsCopy = needs; + } + + /** + * Does this component need a copy. + * @return Whether it does. + */ + public boolean getNeedsCopy() + { + return fNeedsCopy; + } } diff --git a/source/java/org/alfresco/repo/avm/OrphanReaper.java b/source/java/org/alfresco/repo/avm/OrphanReaper.java new file mode 100644 index 0000000000..37ac01f304 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/OrphanReaper.java @@ -0,0 +1,125 @@ +/* + * 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; + +/** + * This is the background thread for reaping no longer referenced nodes + * in the AVM repository. These orphans arise from purge operations. + * @author britt + */ +class OrphanReaper implements Runnable +{ + /** + * Inactive base sleep interval. + */ + private long fInactiveBaseSleep; + + /** + * Active base sleep interval. + */ + private long fActiveBaseSleep; + + /** + * Batch size. + */ + private int fBatchSize; + + /** + * Flag for shutting down this. + */ + private boolean fDone; + + /** + * The thread for this. + */ + private Thread fThread; + + /** + * Create one with default parameters. + */ + public OrphanReaper() + { + fInactiveBaseSleep = 30000; + fActiveBaseSleep = 2000; + fBatchSize = 50; + fDone = false; + } + + // Setters for configuration. + + /** + * Set the Inactive Base Sleep interval. + * @param interval The interval to set in ms. + */ + public void setInactiveBaseSleep(long interval) + { + fInactiveBaseSleep = interval; + } + + /** + * Set the active base sleep interval. + * @param interval The interval to set in ms. + */ + public void setActiveBaseSleep(long interval) + { + fActiveBaseSleep = interval; + } + + /** + * Set the batch size. + * @param size The batch size to set. + */ + public void setBatchSize(int size) + { + fBatchSize = size; + } + + /** + * Start things up after configuration is complete. + */ + public void init() + { + fThread = new Thread(this); + fThread.start(); + } + + /** + * Shutdown the reaper. This needs to be called when + * the application shuts down. + */ + public void shutDown() + { + fDone = true; + try + { + fThread.join(); + } + catch (InterruptedException ie) + { + // Do nothing. + } + } + + /** + * Sit in a loop, periodically querying for orphans. When orphans + * are found, unhook them in bite sized batches. + */ + public void run() + { + } +} diff --git a/source/java/org/alfresco/repo/avm/PlainDirectoryNode.java b/source/java/org/alfresco/repo/avm/PlainDirectoryNode.java index 8f280965f1..dc660de4a9 100644 --- a/source/java/org/alfresco/repo/avm/PlainDirectoryNode.java +++ b/source/java/org/alfresco/repo/avm/PlainDirectoryNode.java @@ -1,267 +1,10 @@ -/* - * 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.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -import org.alfresco.repo.avm.hibernate.BasicAttributesBean; -import org.alfresco.repo.avm.hibernate.BasicAttributesBeanImpl; -import org.alfresco.repo.avm.hibernate.DirectoryEntry; -import org.alfresco.repo.avm.hibernate.PlainDirectoryNodeBean; -import org.alfresco.repo.avm.hibernate.PlainDirectoryNodeBeanImpl; /** - * A plain directory. No monkey tricks except for possiblyCopy. + * Interface for PlainDirectoryNodes. * @author britt */ -public class PlainDirectoryNode extends DirectoryNode +interface PlainDirectoryNode extends DirectoryNode { - /** - * The Bean data. - */ - private PlainDirectoryNodeBean fData; - - /** - * Make up a new directory with nothing in it. - * @param repo - */ - public PlainDirectoryNode(Repository repo) - { - // Make up initial BasicAttributes. - long time = System.currentTimeMillis(); - // TODO figure out how to get user information from context. - BasicAttributesBean attrs = new BasicAttributesBeanImpl("britt", - "britt", - "britt", - time, - time, - time); - fData = new PlainDirectoryNodeBeanImpl(repo.getSuperRepository().issueID(), - repo.getLatestVersion(), - 0L, - null, - null, - null, - repo.getDataBean(), - attrs, - false); - repo.getSuperRepository().getSession().save(fData); - setDataBean(fData); - } - - /** - * Make one up from its bean data. Used when a PlainDirectory is - * restored from the database. - * @param data The bean data. - */ - public PlainDirectoryNode(PlainDirectoryNodeBean data) - { - fData = data; - setDataBean(data); - } - - /** - * Copy like constructor. - * @param other The other directory. - * @param repos The Repository Object that will own us. - */ - public PlainDirectoryNode(PlainDirectoryNode other, - Repository repos) - { - // Make up appropriate BasicAttributes. - long time = System.currentTimeMillis(); - // TODO Need to figure out how to get user information from context. - BasicAttributesBean attrs = new BasicAttributesBeanImpl(other.getDataBean().getBasicAttributes()); - attrs.setModDate(time); - attrs.setCreateDate(time); - attrs.setAccessDate(time); - attrs.setCreator("britt"); - attrs.setLastModifier("britt"); - fData = new PlainDirectoryNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repos.getDataBean(), - attrs, - false); - setDataBean(fData); - fData.setChildren(new HashMap(((PlainDirectoryNodeBean)other.getDataBean()).getChildren())); - } - - /** - * Add a child to this directory, possibly doing a copy. - * @param name The name of the child. - * @param child The child node. - * @param lPath The lookup path to this directory. - * @return Success or failure. - */ - public boolean addChild(String name, AVMNode child, Lookup lPath) - { - // No, if a child with the given name exists. Note that uniqueness - // of names is built into the AVM, as opposed to being configurable. - if (fData.getChildren().containsKey(name)) - { - return false; - } - DirectoryNode toModify = (DirectoryNode)copyOnWrite(lPath); - toModify.putChild(name, child); - child.setParent(toModify); - child.setRepository(lPath.getRepository()); - return true; - } - - /** - * Does this directory directly contain the given node. - * @param node The node to check. - * @return Whether it was found. - */ - public boolean directlyContains(AVMNode node) - { - // TODO This is inefficient; maybe use a two way map. - DirectoryEntry entry = new DirectoryEntry(node.getType(), node.getDataBean()); - return fData.getChildren().containsValue(entry); - } - - /** - * Get a directory listing. - * @param lPath The lookup path. - * @param version Which version. - * @return The listing. - */ - public Map getListing(Lookup lPath, int version) - { - // Maybe this is pointless, but it's nice to be able to iterate - // over entries in a defined order. - return new TreeMap(fData.getChildren()); - } - - /** - * Lookup a child by name. - * @param lPath The lookup path so far. - * @param name The name to lookup. - * @param version The version to look under. - * @return The child or null. - */ - public AVMNode lookupChild(Lookup lPath, String name, int version) - { - DirectoryEntry child = fData.getChildren().get(name); - if (child == null) - { - return null; - } - return AVMNodeFactory.CreateFromBean(child.getChild()); - } - - /** - * Remove a child, no copying. - * @param name The name of the child to remove. - */ - public void rawRemoveChild(String name) - { - fData.getChildren().remove(name); - } - - /** - * Remove a child. Possibly copy. - * @param name The name of the child to remove. - * @param lPath The lookup path. - * @return Success or failure. - */ - public boolean removeChild(String name, Lookup lPath) - { - // Can't remove it if it's not there. - if (!fData.getChildren().containsKey(name)) - { - return false; - } - DirectoryNode toModify = (DirectoryNode)copyOnWrite(lPath); - toModify.rawRemoveChild(name); - return true; - } - - /** - * Put a new child node into this directory. No copy. - * @param name The name of the child. - * @param node The node to add. - */ - public void putChild(String name, AVMNode node) - { - fData.getChildren().put(name, new DirectoryEntry(node.getType(), node.getDataBean())); - } - - // TODO I don't think this is at all necessary in the world without - // mounted VirtualRepositories. - /** - * Set repository after copy on write. - * @param parent The parent after copy on write. - */ - public void handlePostCopy(DirectoryNode parent) - { - } - - /** - * Copy on write logic. - * @param lPath The lookup path. - * @return - */ - public AVMNode possiblyCopy(Lookup lPath) - { - if (!shouldBeCopied()) - { - return null; - } - // Otherwise do an actual copy. - DirectoryNode newMe = null; - long newBranchID = lPath.getHighestBranch(); - // In a layered context a copy on write creates a new - // layered directory. - if (lPath.isLayered()) - { - newMe = new LayeredDirectoryNode(this, lPath.getRepository(), lPath); - } - else - { - newMe = new PlainDirectoryNode(this, lPath.getRepository()); - } - newMe.setAncestor(this); - newMe.setBranchID(newBranchID); - return newMe; - } - - /** - * Get the type of this node. - * @return The type of this node. - */ - public int getType() - { - return AVMNodeType.PLAIN_DIRECTORY; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMNode#toString(org.alfresco.repo.avm.Lookup) - */ - @Override - public String toString(Lookup lPath) - { - return "[PD:" + fData.getId() + "]"; - } -} +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java b/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java new file mode 100644 index 0000000000..ed4fcccb9c --- /dev/null +++ b/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java @@ -0,0 +1,379 @@ +/* + * 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.Serializable; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.hibernate.LockMode; +import org.hibernate.Session; +import org.hibernate.proxy.HibernateProxy; + +/** + * A plain directory. No monkey tricks except for possiblyCopy. + * @author britt + */ +class PlainDirectoryNodeImpl extends DirectoryNodeImpl implements PlainDirectoryNode +{ + static final long serialVersionUID = 9423813734583003L; + + /** + * Whether this is a root node. + */ + private boolean fIsRoot; + + /** + * Make up a new directory with nothing in it. + * @param repo + */ + public PlainDirectoryNodeImpl(Repository repo) + { + super(repo.getSuperRepository().issueID(), repo); + fIsRoot = false; + repo.getSuperRepository().getSession().save(this); + SuperRepository.GetInstance().getSession().flush(); + } + + /** + * Anonymous constructor. + */ + protected PlainDirectoryNodeImpl() + { + } + + /** + * Copy like constructor. + * @param other The other directory. + * @param repos The Repository Object that will own us. + */ + @SuppressWarnings("unchecked") + public PlainDirectoryNodeImpl(PlainDirectoryNode other, + Repository repos) + { + super(repos.getSuperRepository().issueID(), repos); + Session sess = repos.getSuperRepository().getSession(); + sess.save(this); + sess.flush(); + for (ChildEntry child : other.getChildren()) + { + ChildEntry newChild = new ChildEntryImpl(child.getName(), + this, + child.getChild()); + sess.save(newChild); + } + } + + /** + * Add a child to this directory, possibly doing a copy. + * @param name The name of the child. + * @param child The child node. + * @param lPath The lookup path to this directory. + * @return Success or failure. + */ + public boolean addChild(String name, AVMNode child, Lookup lPath) + { + // No, if a child with the given name exists. Note that uniqueness + // of names is built into the AVM, as opposed to being configurable. + if (getChild(name) != null) + { + return false; + } + DirectoryNode toModify = (DirectoryNode)copyOnWrite(lPath); + toModify.putChild(name, child); + child.setParent(toModify); + child.setRepository(lPath.getRepository()); + return true; + } + + /** + * Does this directory directly contain the given node. + * @param node The node to check. + * @return Whether it was found. + */ + public boolean directlyContains(AVMNode node) + { + return getChild(node) != null; + } + + /** + * Get a directory listing. + * @param lPath The lookup path. + * @param version Which version. + * @return The listing. + */ + @SuppressWarnings("unchecked") + public Map getListing(Lookup lPath) + { + TreeMap result = new TreeMap(); + List children = getChildren(); + for (ChildEntry child : children) + { + result.put(child.getName(), child.getChild()); + } + return result; + } + + /** + * Get a listing of from a directory node descriptor. + * @param dir The directory node descriptor. + * @return A Map of names to node descriptors. + */ + public Map getListing(AVMNodeDescriptor dir) + { + if (dir.getPath() == null) + { + throw new AVMBadArgumentException("Path is null."); + } + TreeMap result = new TreeMap(); + List children = getChildren(); + for (ChildEntry child : children) + { + result.put(child.getName(), + child.getChild().getDescriptor(dir.getPath(), child.getName(), dir.getIndirection())); + } + return result; + } + + /** + * Lookup a child by name. + * @param lPath The lookup path so far. + * @param name The name to lookup. + * @param version The version to look under. + * @return The child or null. + */ + @SuppressWarnings("unchecked") + public AVMNode lookupChild(Lookup lPath, String name, int version) + { + // We're doing the hand unrolling of the proxy because + // Hibernate/CGLIB proxies are broken. + ChildEntry entry = getChild(name); + if (entry == null) + { + return null; + } + AVMNode child = entry.getChild(); + if (child instanceof HibernateProxy) + { + HibernateProxy proxy = (HibernateProxy)child; + return (AVMNode)proxy.getHibernateLazyInitializer().getImplementation(); + } + return child; + } + + /** + * Lookup a child using a node descriptor as context. + * @param mine The node descriptor for this. + * @param name The name of the child to lookup. + * @return A node descriptor for the child. + */ + public AVMNodeDescriptor lookupChild(AVMNodeDescriptor mine, String name) + { + if (mine.getPath() == null) + { + throw new AVMBadArgumentException("Path is null."); + } + ChildEntry entry = getChild(name); + if (entry == null) + { + return null; + } + return entry.getChild().getDescriptor(mine.getPath(), name, (String)null); + } + + /** + * Remove a child, no copying. + * @param name The name of the child to remove. + */ + @SuppressWarnings("unchecked") + public void rawRemoveChild(String name) + { + ChildEntry entry = getChild(name); + if (entry != null) + { + SuperRepository.GetInstance().getSession().delete(entry); + } + } + + /** + * Remove a child. Possibly copy. + * @param name The name of the child to remove. + * @param lPath The lookup path. + * @return Success or failure. + */ + public boolean removeChild(String name, Lookup lPath) + { + // Can't remove it if it's not there. + if (getChild(name) == null) + { + return false; + } + DirectoryNode toModify = (DirectoryNode)copyOnWrite(lPath); + toModify.rawRemoveChild(name); + return true; + } + + /** + * Put a new child node into this directory. No copy. + * @param name The name of the child. + * @param node The node to add. + */ + public void putChild(String name, AVMNode node) + { + Session sess = SuperRepository.GetInstance().getSession(); +// sess.lock(this, LockMode.UPGRADE); + sess.flush(); + ChildEntry entry = new ChildEntryImpl(name, this, node); + ChildEntry existing = (ChildEntry)sess.get(ChildEntryImpl.class, (Serializable)entry); + if (existing != null) + { + existing.setChild(node); + } + else + { + sess.save(entry); + } + } + + /** + * Copy on write logic. + * @param lPath The lookup path. + * @return + */ + public AVMNode possiblyCopy(Lookup lPath) + { + if (!lPath.needsCopying()) + { + return null; + } + // Otherwise do an actual copy. + DirectoryNode newMe = null; + // In a layered context a copy on write creates a new + // layered directory. + if (lPath.isLayered()) + { + // Subtlety warning: This distinguishes the case of a + // Directory that was branched into the layer and one + // that is indirectly seen in this layer. + newMe = new LayeredDirectoryNodeImpl(this, lPath.getRepository(), lPath, + lPath.isInThisLayer()); + ((LayeredDirectoryNodeImpl)newMe).setLayerID(lPath.getTopLayer().getLayerID()); + } + else + { + newMe = new PlainDirectoryNodeImpl(this, lPath.getRepository()); + } + newMe.setAncestor(this); + return newMe; + } + + /** + * Get the type of this node. + * @return The type of this node. + */ + public int getType() + { + return AVMNodeType.PLAIN_DIRECTORY; + } + + /** + * Get a diagnostic String representation. + * @param lPath The Lookup. + * @return A diagnostic String representation. + */ + public String toString(Lookup lPath) + { + return "[PD:" + getId() + "]"; + } + + /** + * Turn this into a primary indirection. This must be in a + * layered context. + * @param lPath The Lookup. + */ + public void turnPrimary(Lookup lPath) + { + LayeredDirectoryNode toModify = (LayeredDirectoryNode)copyOnWrite(lPath); + Lookup lookup = lPath.getRepository().getSuperRepository().lookup(-1, lPath.getRepresentedPath()); + toModify.rawSetPrimary(lookup.getCurrentIndirection()); + } + + /** + * Retarget this directory. lPath must be in a layered context. + * @param lPath The Lookup. + * @param target The target path. + */ + public void retarget(Lookup lPath, String target) + { + LayeredDirectoryNode toModify = (LayeredDirectoryNode)copyOnWrite(lPath); + toModify.rawSetPrimary(target); + } + + /** + * Get the descriptor for this node. + * @param The Lookup. + * @return A descriptor. + */ + public AVMNodeDescriptor getDescriptor(Lookup lPath) + { + BasicAttributes attrs = getBasicAttributes(); + return new AVMNodeDescriptor(lPath.getRepresentedPath(), + AVMNodeType.PLAIN_DIRECTORY, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + null, + false, + -1, + -1); + } + + /** + * Get this node's descriptor. + * @param parentPath The parent path. + * @param name The name that we were looked up under. + * @param parentIndirection The parent indirection. + * @return This node's node descriptor + */ + public AVMNodeDescriptor getDescriptor(String parentPath, String name, String parentIndirection) + { + BasicAttributes attrs = getBasicAttributes(); + String path = parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name; + return new AVMNodeDescriptor(path, + AVMNodeType.PLAIN_DIRECTORY, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + null, + false, + -1, + -1); + } +} + diff --git a/source/java/org/alfresco/repo/avm/PlainFileNode.java b/source/java/org/alfresco/repo/avm/PlainFileNode.java index 41f6872ca1..80b603cbc3 100644 --- a/source/java/org/alfresco/repo/avm/PlainFileNode.java +++ b/source/java/org/alfresco/repo/avm/PlainFileNode.java @@ -1,204 +1,10 @@ -/* - * 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 org.alfresco.repo.avm.hibernate.BasicAttributesBean; -import org.alfresco.repo.avm.hibernate.BasicAttributesBeanImpl; -import org.alfresco.repo.avm.hibernate.PlainFileNodeBean; -import org.alfresco.repo.avm.hibernate.PlainFileNodeBeanImpl; - /** - * A plain old file. Contains a Content object. + * Interface for Plain file nodes. * @author britt */ -public class PlainFileNode extends FileNode +interface PlainFileNode extends FileNode { - /** - * The data bean. - */ - private PlainFileNodeBean fData; - - /** - * Construct one from its data bean. - * @param data The data bean. - */ - public PlainFileNode(PlainFileNodeBean data) - { - fData = data; - setDataBean(data); - } - - /** - * Make one from just a repository. - * This is the constructor used when a brand new plain file is being made. - * @param repos A Repository. - */ - public PlainFileNode(Repository repos) - { - FileContent content = new FileContent(repos.getSuperRepository()); - long time = System.currentTimeMillis(); - BasicAttributesBean attrs = new BasicAttributesBeanImpl("britt", - "britt", - "britt", - time, - time, - time); - fData = new PlainFileNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repos.getDataBean(), - attrs, - content.getDataBean()); - content.setRefCount(1); - // Transitive persistence should take care of content. - repos.getSuperRepository().getSession().save(fData); - setDataBean(fData); - } - - /** - * Copy on write constructor. - * @param other The node we are being copied from. - * @param repos The Repository. - */ - public PlainFileNode(PlainFileNode other, - Repository repos) - { - // Setup sensible BasicAttributes. - long time = System.currentTimeMillis(); - // TODO Figure out how to get user from context. - BasicAttributesBean attrs = new BasicAttributesBeanImpl(other.getDataBean().getBasicAttributes()); - attrs.setCreateDate(time); - attrs.setModDate(time); - attrs.setAccessDate(time); - attrs.setCreator("britt"); - attrs.setLastModifier("britt"); - fData = new PlainFileNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repos.getDataBean(), - attrs, - other.fData.getContent()); - repos.getSuperRepository().getSession().save(fData); - fData.getContent().setRefCount(fData.getContent().getRefCount() + 1); - setDataBean(fData); - } - - /** - * Constructor that takes a FileContent to share. - * @param content The FileContent to share. - * @param repos The Repository. - */ - public PlainFileNode(FileContent content, - Repository repos, - BasicAttributesBean oAttrs) - { - // Setup sensible BasicAttributes. - long time = System.currentTimeMillis(); - // TODO Figure out how to get user from context. - - BasicAttributesBean attrs = new BasicAttributesBeanImpl(oAttrs); - attrs.setCreateDate(time); - attrs.setModDate(time); - attrs.setAccessDate(time); - attrs.setCreator("britt"); - attrs.setLastModifier("britt"); - fData = new PlainFileNodeBeanImpl(repos.getSuperRepository().issueID(), - -1, - 0, - null, - null, - null, - repos.getDataBean(), - attrs, - content.getDataBean()); - repos.getSuperRepository().getSession().save(fData); - fData.getContent().setRefCount(fData.getContent().getRefCount() + 1); - setDataBean(fData); - } - - /** - * Copy on write logic. - * @param lPath The lookup path. - */ - public AVMNode possiblyCopy(Lookup lPath) - { - if (!shouldBeCopied()) - { - return null; - } - PlainFileNode newMe = new PlainFileNode(this, lPath.getRepository()); - newMe.setAncestor(this); - newMe.setBranchID(lPath.getHighestBranch()); - return newMe; - } - - /** - * Get the type of this node. - * @return The type. - */ - public int getType() - { - return AVMNodeType.PLAIN_FILE; - } - - /** - * Get content for reading. - */ - public FileContent getContentForRead(int version, Repository repo) - { - return new FileContent(fData.getContent()); - } - - /** - * Get content for writing. - * @param repo The Repository. - */ - public FileContent getContentForWrite(Repository repo) - { - FileContent fc = new FileContent(fData.getContent()); - if (fData.getContent().getRefCount() > 1) - { - fc = new FileContent(fc, repo.getSuperRepository()); - fData.setContent(fc.getDataBean()); - } - return fc; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMNode#toString(org.alfresco.repo.avm.Lookup) - */ - @Override - public String toString(Lookup lPath) - { - return "[PF:" + fData.getId() + "]"; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMNode#handlePostCopy(org.alfresco.repo.avm.DirectoryNode) - */ - @Override - public void handlePostCopy(DirectoryNode parent) - { - } + public FileContent getContent(); } diff --git a/source/java/org/alfresco/repo/avm/PlainFileNodeImpl.java b/source/java/org/alfresco/repo/avm/PlainFileNodeImpl.java new file mode 100644 index 0000000000..d703e48b77 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/PlainFileNodeImpl.java @@ -0,0 +1,212 @@ +/* + * 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; + + +/** + * A plain old file. Contains a Content object. + * @author britt + */ +class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode +{ + static final long serialVersionUID = 8720376837929735294L; + + /** + * The file content. + */ + private FileContent fContent; + + /** + * Default constructor. + */ + protected PlainFileNodeImpl() + { + } + + /** + * Make one from just a repository. + * This is the constructor used when a brand new plain file is being made. + * @param repos A Repository. + * @param source A possibly null stream from which to get data. + */ + public PlainFileNodeImpl(Repository repos) + { + super(repos.getSuperRepository().issueID(), repos); + fContent = new FileContentImpl(repos.getSuperRepository()); + repos.getSuperRepository().getSession().save(this); + } + + /** + * Copy on write constructor. + * @param other The node we are being copied from. + * @param repos The Repository. + */ + public PlainFileNodeImpl(PlainFileNode other, + Repository repos) + { + super(repos.getSuperRepository().issueID(), repos); + fContent = other.getContent(); + fContent.setRefCount(fContent.getRefCount() + 1); + repos.getSuperRepository().getSession().save(this); + } + + /** + * Constructor that takes a FileContent to share. + * @param content The FileContent to share. + * @param repos The Repository. + */ + public PlainFileNodeImpl(FileContent content, + Repository repos, + BasicAttributes oAttrs) + { + super(repos.getSuperRepository().issueID(), repos); + fContent = content; + fContent.setRefCount(fContent.getRefCount() + 1); + repos.getSuperRepository().getSession().save(this); + } + + /** + * Copy on write logic. + * @param lPath The lookup path. + */ + public AVMNodeImpl possiblyCopy(Lookup lPath) + { + if (!lPath.needsCopying()) + { + return null; + } + PlainFileNodeImpl newMe = new PlainFileNodeImpl(this, lPath.getRepository()); + newMe.setAncestor(this); + return newMe; + } + + /** + * Get the type of this node. + * @return The type. + */ + public int getType() + { + return AVMNodeType.PLAIN_FILE; + } + + /** + * Get content for reading. + */ + public FileContent getContentForRead(Repository repo) + { + return fContent; + } + + /** + * Get content for writing. + * @param repo The Repository. + */ + public FileContent getContentForWrite(Repository repo) + { + if (fContent.getRefCount() > 1) + { + fContent = new FileContentImpl(fContent, repo.getSuperRepository()); + } + return fContent; + } + + /** + * Get a diagnostic string representation. + * @param lPath The Lookup. + * @return A diagnostic String representation. + */ +// @Override + public String toString(Lookup lPath) + { + return "[PF:" + getId() + "]"; + } + + /** + * Get the descriptor for this node. + * @param The Lookup. + * @return A descriptor. + */ + public AVMNodeDescriptor getDescriptor(Lookup lPath) + { + BasicAttributes attrs = getBasicAttributes(); + return new AVMNodeDescriptor(lPath.getRepresentedPath(), + AVMNodeType.PLAIN_FILE, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + null, + false, + -1, + getContentForRead( + lPath.getRepository()) + .getLength(lPath.getRepository().getSuperRepository())); + } + + /** + * Get the descriptor for this. + * @param parentPath The parent path. + * @param name The name this was looked up with. + * @param parentIndirection The parent indirection. + * @return The descriptor for this. + */ + public AVMNodeDescriptor getDescriptor(String parentPath, String name, String parentIndirection) + { + BasicAttributes attrs = getBasicAttributes(); + String path = parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name; + return new AVMNodeDescriptor(path, + AVMNodeType.PLAIN_FILE, + attrs.getCreator(), + attrs.getOwner(), + attrs.getLastModifier(), + attrs.getCreateDate(), + attrs.getModDate(), + attrs.getAccessDate(), + getId(), + getVersionID(), + null, + false, + -1, + getContentForRead( + getRepository()) + .getLength(getRepository().getSuperRepository())); + } + + /** + * Get the file content of this node. + * @return The file content object. + */ + public FileContent getContent() + { + return fContent; + } + + /** + * Set the FileContent for this file. + * @param content + */ + protected void setContent(FileContent content) + { + fContent = content; + } +} + diff --git a/source/java/org/alfresco/repo/avm/Repository.java b/source/java/org/alfresco/repo/avm/Repository.java index 2d399e5511..23b4b0f681 100644 --- a/source/java/org/alfresco/repo/avm/Repository.java +++ b/source/java/org/alfresco/repo/avm/Repository.java @@ -14,158 +14,193 @@ * 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.List; import java.util.Set; -import org.alfresco.repo.avm.hibernate.RepositoryBean; - - /** - * This is the interface between low level repository objects - * and the high level implementation of the repository. + * The repository interface. Methods for filesystem like, versioning, + * and layering operations. * @author britt */ -public interface Repository +interface Repository { /** - * Inform this Repository that a Node is new and should - * therefore not be copied. - * @param node The node that is reporting itself. + * This returns the next version in this repository that will be snapshotted. + * @return The next version to be snapshotted. */ - public void setNew(AVMNode node); - + public int getNextVersionID(); + /** - * Get the latest version id. - * @return The latest version. - */ - public int getLatestVersion(); - - /** - * Inform this Repository that the root node is new. - * @param root The new root directory node. + * Set a new root for this repository. + * @param root The root to set. */ public void setNewRoot(DirectoryNode root); - + /** - * Make a snapshot. Equivalent of subversion end of commit. A new - * version number is issued. + * Snapshots this repository. This sets all nodes in the + * the repository to the should be copied state, and creates + * a new version root. */ public void createSnapshot(); /** * Create a new directory. - * @param path The path to the directory for creation. - * @param name The name for the new directory. + * @param path The path to the parent directory. + * @param name The name to give the new directory. */ public void createDirectory(String path, String name); /** - * Create a layered directory over srcPath at dstPath/name. - * @param srcPath Fully qualified path. - * @param dstPath Repository path to target directory. - * @param name What the new layered directory should be called. + * Create a new layered directory. + * @param srcPath The path that the new layered directory will point at. + * @param dstPath The path to the directory to create the new layered directory in. + * @param name The name of the new layered directory. */ - public void createLayeredDirectory(String srcPath, String dstPath, String name); + public void createLayeredDirectory(String srcPath, String dstPath, + String name); /** - * Create a new empty file. - * @param path The path to the directory for creation. - * @param name The name for the new file. + * Create a new file. The designated file cannot already exist. + * @param path The path to the directory to contain the new file. + * @param name The name to give the new file. + * @param source An InputStream of data to put in the file. May be null. */ - public void createFile(String path, String name); + public OutputStream createFile(String path, String name); /** - * Create a layered file over srcPath at dstPath/name - * @param srcPath Fully qualified path. - * @param dstPath Repository path. - * @param name The name the new layered file should have. + * Create a new layered file. + * @param srcPath The target path for the new file. + * @param dstPath The path to the directory to make the new file in. + * @param name The name of the new file. */ public void createLayeredFile(String srcPath, String dstPath, String name); - + /** - * Get an input stream from an existing file. - * TODO Figure out nio way of doing things. - * @param version The version id to look under. + * Get an InputStream from a file. + * @param version The version to look under. * @param path The path to the file. - * @return An InputStream. + * @return An InputStream */ public InputStream getInputStream(int version, String path); - + /** - * Get a directory listing. + * Get a listing of the designated directory. * @param version The version to look under. * @param path The path to the directory. - * @return A List of FolderEntries. + * @return A listing. */ public List getListing(int version, String path); - + /** - * Get an OutputStream to an existing file. This may trigger a copy. + * Get an output stream to a file. * @param path The path to the file. + * @return An OutputStream */ public OutputStream getOutputStream(String path); /** - * Remove a node from a directory. - * @param path The path to the directory. + * Get a random access file to the given file. + * @param version The version id (read-only if not -1) + * @param path The path to the file. + * @param access The access for RandomAccessFile. + * @return A RandomAccessFile. + */ + public RandomAccessFile getRandomAccess(int version, String path, String access); + + /** + * Remove a node and all of its contents. + * @param path The path to the node's parent directory. * @param name The name of the node to remove. */ public void removeNode(String path, String name); - + /** - * This moves a node from one place in a layer to another place in - * the same layer without leaving a deleted entry in the source directory. - * @param srcPath The path containing the src node. - * @param srcName The name of the src node. - * @param dstPath The destination container. - * @param dstName The name of the destination node. + * Uncover a whited out node. + * @param dirPath The path to the directory. + * @param name The name to uncover. */ - public void slide(String srcPath, String srcName, String dstPath, String dstName); - + public void uncover(String dirPath, String name); + + // TODO This is problematic. As time goes on this returns + // larger and larger data sets. Perhaps what we should do is + // provide methods for getting versions by date range, n most + // recent etc. /** - * Get the version ids that this Repository has. - * @return A Set of Version IDs. + * Get all the version ids for this repository. + * @return A Set of all versions. */ public Set getVersions(); - - /** - * Get the data bean. - * @return The data bean. - */ - public RepositoryBean getDataBean(); - + /** * Get the super repository. * @return The SuperRepository. */ public SuperRepository getSuperRepository(); - + /** - * Get a lookup object for a path. + * Lookup a node. * @param version The version to look under. - * @param path The Repository path. - * @return The Lookup. + * @param path The path to the node. + * @return A Lookup object. */ public Lookup lookup(int version, String path); - + /** - * Get a lookup object for a path. Directory only. + * Lookup a directory. * @param version The version to look under. - * @param path The Repository path. - * @return The Lookup. + * @param path The path to the directory. + * @return A Lookup object. */ public Lookup lookupDirectory(int version, String path); - + /** - * Get the indirection path of a layered node. - * @param version The version id. - * @param path The Repository path. + * For a layered node, get its indirection. + * @param version The version to look under. + * @param path The path to the node. * @return The indirection. */ public String getIndirectionPath(int version, String path); -} + + /** + * Make the indicated directory a primary indirection. + * @param path The Repository relative path. + */ + public void makePrimary(String path); + + /** + * Change the target of a layered directory. + * @param path The path to the layered directory. + * @param target The new target path. + */ + public void retargetLayeredDirectory(String path, String target); + + /** + * Get the root directory of this Repository. + * @return The root directory. + */ + public DirectoryNode getRoot(); + + /** + * Get the specified root as a descriptor. + * @param version The version to get (-1 for head). + * @return The specified root or null. + */ + public AVMNodeDescriptor getRoot(int version); + + /** + * Get the name of this repository. + * @return The name. + */ + public String getName(); + + /** + * Purge all the nodes reachable only by the given version. + * @param version + */ + public void purgeVersion(int version); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/RepositoryImpl.java b/source/java/org/alfresco/repo/avm/RepositoryImpl.java new file mode 100644 index 0000000000..07efca62ef --- /dev/null +++ b/source/java/org/alfresco/repo/avm/RepositoryImpl.java @@ -0,0 +1,732 @@ +/* + * 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.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.hibernate.Query; +import org.hibernate.proxy.HibernateProxy; + +/** + * A Repository contains a current root directory and a list of + * root versions. Each root version corresponds to a separate snapshot + * operation. + * @author britt + */ +class RepositoryImpl implements Repository, Serializable +{ + static final long serialVersionUID = -1485972568675732904L; + + /** + * The name of this repository. + */ + private String fName; + + /** + * The current root directory. + */ + private DirectoryNode fRoot; + + /** + * The next version id. + */ + private int fNextVersionID; + + /** + * The version (for concurrency control). + */ + private long fVers; + + /** + * The super repository. + */ + transient private SuperRepository fSuper; + + /** + * Default constructor. + */ + protected RepositoryImpl() + { + fSuper = SuperRepository.GetInstance(); + } + + /** + * Make a brand new repository. + * @param superRepo The SuperRepository. + * @param name The name of the Repository. + */ + public RepositoryImpl(SuperRepository superRepo, String name) + { + // Make ourselves up and save. + fSuper = superRepo; + fName = name; + fNextVersionID = 0; + fRoot = null; + fSuper.getSession().save(this); + // Make up the initial version record and save. + long time = System.currentTimeMillis(); + fRoot = new PlainDirectoryNodeImpl(this); + fRoot.setIsNew(false); + fRoot.setIsRoot(true); + fSuper.getSession().save(fRoot); + VersionRoot versionRoot = new VersionRootImpl(this, + fRoot, + fNextVersionID, + time, + "britt"); + fNextVersionID++; + fSuper.getSession().save(versionRoot); + } + + /** + * Set a new root for this. + * @param root + */ + public void setNewRoot(DirectoryNode root) + { + fRoot = root; + fRoot.setIsRoot(true); + } + + /** + * Snapshot this repository. This creates a new version record. + */ + @SuppressWarnings("unchecked") + public void createSnapshot() + { + // Clear out the new nodes. + Query query = + fSuper.getSession().getNamedQuery("AVMNode.ByNewInRepo"); + query.setEntity("repo", this); + for (AVMNode newNode : (List)query.list()) + { + newNode.setIsNew(false); + } + // Make up a new version record. + VersionRoot versionRoot = new VersionRootImpl(this, + fRoot, + fNextVersionID, + System.currentTimeMillis(), + "britt"); + fSuper.getSession().save(versionRoot); + // Increment the version id. + fNextVersionID++; + } + + /** + * Create a new directory. + * @param path The path to the containing directory. + * @param name The name of the new directory. + */ + public void createDirectory(String path, String name) + { + Lookup lPath = lookupDirectory(-1, path); +// lPath.acquireLocks(); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (dir.lookupChild(lPath, name, -1) != null) + { + throw new AVMExistsException("Child exists: " + name); + } + DirectoryNode newDir = null; + if (lPath.isLayered()) // Creating a directory in a layered context creates + // a LayeredDirectoryNode that gets its indirection from + // its parent. + { + newDir = new LayeredDirectoryNodeImpl((String)null, this); + ((LayeredDirectoryNodeImpl)newDir).setPrimaryIndirection(false); + ((LayeredDirectoryNodeImpl)newDir).setLayerID(lPath.getTopLayer().getLayerID()); + } + else + { + newDir = new PlainDirectoryNodeImpl(this); + } + newDir.setVersionID(getNextVersionID()); + dir.addChild(name, newDir, lPath); + } + + /** + * Create a new layered directory. + * @param srcPath The target indirection for a layered node. + * @param dstPath The containing directory for the new node. + * @param name The name of the new node. + */ + public void createLayeredDirectory(String srcPath, String dstPath, + String name) + { + Lookup lPath = lookupDirectory(-1, dstPath); +// lPath.acquireLocks(); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (dir.lookupChild(lPath, name, -1) != null) + { + throw new AVMExistsException("Child exists: " + name); + } + LayeredDirectoryNode newDir = + new LayeredDirectoryNodeImpl(srcPath, this); + if (lPath.isLayered()) + { + // When a layered directory is made inside of a layered context, + // it gets its layer id from the topmost layer in its lookup + // path. + LayeredDirectoryNode top = lPath.getTopLayer(); + newDir.setLayerID(top.getLayerID()); + } + else + { + // Otherwise we issue a brand new layer id. + newDir.setLayerID(fSuper.issueLayerID()); + } + dir.addChild(name, newDir, lPath); + newDir.setVersionID(getNextVersionID()); + } + + /** + * Create a new file. + * @param path The path to the directory to contain the new file. + * @param name The name to give the new file. + * @param source A (possibly null) InputStream from which to get + * initial content. + */ + public OutputStream createFile(String path, String name) + { + Lookup lPath = lookupDirectory(-1, path); +// lPath.acquireLocks(); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (dir.lookupChild(lPath, name, -1) != null) + { + throw new AVMExistsException("Child exists: " + name); + } + PlainFileNodeImpl file = new PlainFileNodeImpl(this); + file.setVersionID(getNextVersionID()); + dir.addChild(name, file, lPath); + return file.getContentForWrite(this).getOutputStream(fSuper); + } + + /** + * Create a new layered file. + * @param srcPath The target indirection for the layered file. + * @param dstPath The path to the directory to contain the new file. + * @param name The name of the new file. + */ + public void createLayeredFile(String srcPath, String dstPath, String name) + { + Lookup lPath = lookupDirectory(-1, dstPath); +// lPath.acquireLocks(); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (dir.lookupChild(lPath, name, -1) != null) + { + throw new AVMExistsException("Child exists: " + name); + } + // TODO Reexamine decision to not check validity of srcPath. + LayeredFileNodeImpl newFile = + new LayeredFileNodeImpl(srcPath, this); + dir.addChild(name, newFile, lPath); + newFile.setVersionID(getNextVersionID()); + } + + /** + * Get an input stream from a file. + * @param version The version id to look under. + * @param path The path to the file. + * @return An InputStream. + */ + public InputStream getInputStream(int version, String path) + { + Lookup lPath = lookup(version, path); + AVMNode node = lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.PLAIN_FILE && + node.getType() != AVMNodeType.LAYERED_FILE) + { + throw new AVMExistsException("Not a file: " + path + " r " + version); + } + FileNode file = (FileNode)node; + FileContent content = file.getContentForRead(this); + return content.getInputStream(fSuper); + } + + /** + * Get a listing from a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @return A List of FolderEntries. + */ + public List getListing(int version, String path) + { + Lookup lPath = lookupDirectory(version, path); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + Map listing = dir.getListing(lPath); + ArrayList results = new ArrayList(); + for (String name : listing.keySet()) + { + AVMNode child = listing.get(name); + FolderEntry item = new FolderEntry(); + item.setName(name); + item.setType(child.getType()); + results.add(item); + } + return results; + } + + /** + * Get an output stream to a file. + * @param path The path to the file. + * @return An OutputStream. + */ + public OutputStream getOutputStream(String path) + { + Lookup lPath = lookup(-1, path); +// lPath.acquireLocks(); + AVMNode node = lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.PLAIN_FILE && + node.getType() != AVMNodeType.LAYERED_FILE) + { + throw new AVMWrongTypeException("Not a file: " + path); + } + FileNode file = (FileNode)node; + file = (FileNode)file.copyOnWrite(lPath); + FileContent content = file.getContentForWrite(this); + return content.getOutputStream(fSuper); // TODO Do we really need fSuper? + } + + /** + * Get a RandomAccessFile to a file node. + * @param version The version. + * @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) + { + boolean write = access.indexOf("rw") == 0; + if (write && version >= 0) + { + throw new AVMException("Access denied: " + path); + } + Lookup lPath = lookup(version, path); + if (write) + { +// lPath.acquireLocks(); + } + AVMNode node = lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.PLAIN_FILE && + node.getType() != AVMNodeType.LAYERED_FILE) + { + throw new AVMWrongTypeException("Not a file: " + path); + } + FileNode file = (FileNode)node; + FileContent content = null; + if (write) + { + file = (FileNode)file.copyOnWrite(lPath); + content = file.getContentForWrite(this); + } + else + { + content = file.getContentForRead(this); + } + return content.getRandomAccess(fSuper, access); + } + + /** + * 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 removeNode(String path, String name) + { + // TODO Are we double checking for existence? + Lookup lPath = lookupDirectory(-1, path); +// lPath.acquireLocks(); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (dir.lookupChild(lPath, name, -1) == null) + { + throw new AVMNotFoundException("Does not exist: " + name); + } + dir.removeChild(name, lPath); + } + + /** + * Allow a name which has been deleted to be visible through that layer. + * @param dirPath The path to the containing directory. + * @param name The name to uncover. + */ + public void uncover(String dirPath, String name) + { + Lookup lPath = lookup(-1, dirPath); +// lPath.acquireLocks(); + AVMNode node = lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.LAYERED_DIRECTORY) + { + throw new AVMWrongTypeException("Not a layered directory: " + dirPath); + } + ((LayeredDirectoryNode)node).uncover(lPath, name); + } + + // TODO This is problematic. As time goes on this returns + // larger and larger data sets. Perhaps what we should do is + // provide methods for getting versions by date range, n most + // recent etc. + /** + * Get the set of all extant version ids for this Repository. + * @return A Set of version ids. + */ + @SuppressWarnings("unchecked") + public Set getVersions() + { + Query query = fSuper.getSession().createQuery("select v.versionID from VersionRootImpl v"); + return new TreeSet((List)query.list()); + } + + /** + * Get the SuperRepository. + * @return The SuperRepository + */ + public SuperRepository getSuperRepository() + { + return fSuper; + } + + /** + * Lookup up a path. + * @param version The version to look in. + * @param path The path to look up. + * @return A Lookup object. + */ + public Lookup lookup(int version, String path) + { + // Make up a Lookup to hold the results. + Lookup result = new Lookup(this, fName); + if (path.length() == 0) + { + throw new AVMException("Invalid path: " + path); + } + if (path.length() > 1) + { + path = path.substring(1); + } + String[] pathElements = path.split("/"); + // Grab the root node to start the lookup. + DirectoryNode dir = null; + // Versions less than 0 mean get current. + if (version < 0) + { + if (fRoot instanceof HibernateProxy) + { + dir = (DirectoryNode)((HibernateProxy)fRoot).getHibernateLazyInitializer().getImplementation(); + } + else + { + dir = fRoot; + } + } + else + { + Query query = + fSuper.getSession().getNamedQuery("VersionRoot.GetVersionRoot"); + query.setEntity("rep", this); + query.setInteger("version", version); + dir = (DirectoryNode)query.uniqueResult(); + if (dir == null) + { + throw new AVMException("Invalid version: " + version); + } + } +// fSuper.getSession().lock(dir, LockMode.READ); + // Add an entry for the root. + result.add(dir, ""); + if (pathElements.length == 0) + { + return result; + } + // Now look up each path element in sequence up to one + // before the end. + for (int i = 0; i < pathElements.length - 1; i++) + { + AVMNode child = dir.lookupChild(result, pathElements[i], version); + if (child == null) + { + throw new AVMNotFoundException("Not found: " + pathElements[i]); + } + // Every element that is not the last needs to be a directory. + if (child.getType() != AVMNodeType.PLAIN_DIRECTORY && + child.getType() != AVMNodeType.LAYERED_DIRECTORY) + { + throw new AVMWrongTypeException("Not a directory: " + pathElements[i]); + } + dir = (DirectoryNode)child; +// fSuper.getSession().lock(dir, LockMode.READ); + result.add(dir, pathElements[i]); + } + // Now look up the last element. + AVMNode child = dir.lookupChild(result, pathElements[pathElements.length - 1], version); + if (child == null) + { + throw new AVMNotFoundException("Not found: " + pathElements[pathElements.length - 1]); + } +// fSuper.getSession().lock(child, LockMode.READ); + result.add(child, pathElements[pathElements.length - 1]); + return result; + } + + /** + * Get the root node descriptor. + * @param version The version to get. + * @return The descriptor. + */ + public AVMNodeDescriptor getRoot(int version) + { + AVMNode root = null; + if (version < 0) + { + root = fRoot; + } + else + { + Query query = + fSuper.getSession().getNamedQuery("VersionRoot.GetVersionRoot"); + query.setEntity("rep", this); + query.setInteger("version", version); + root = (AVMNode)query.uniqueResult(); + if (root == null) + { + throw new AVMException("Invalid version: " + version); + } + } + return root.getDescriptor("main:", "", null); + } + + /** + * Lookup a node and insist that it is a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @return A Lookup object. + */ + public Lookup lookupDirectory(int version, String path) + { + // Just do a regular lookup and assert that the last element + // is a directory. + Lookup lPath = lookup(version, path); + if (lPath.getCurrentNode().getType() != AVMNodeType.PLAIN_DIRECTORY && + lPath.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY) + { + throw new AVMWrongTypeException("Not a directory: " + path); + } + return lPath; + } + + /** + * Get the effective indirection path for a layered node. + * @param version The version to look under. + * @param path The path to the node. + * @return The effective indirection. + */ + public String getIndirectionPath(int version, String path) + { + Lookup lPath = lookup(version, path); + AVMNode node = lPath.getCurrentNode(); + if (node.getType() == AVMNodeType.LAYERED_DIRECTORY) + { + return ((LayeredDirectoryNode)node).getUnderlying(lPath); + } + if (node.getType() == AVMNodeType.LAYERED_FILE) + { + return ((LayeredFileNode)node).getUnderlying(lPath); + } + throw new AVMWrongTypeException("Not a layered node: " + path); + } + + /** + * Make the indicated node a primary indirection. + * @param path The path to the node. + */ + public void makePrimary(String path) + { + Lookup lPath = lookupDirectory(-1, path); +// lPath.acquireLocks(); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!lPath.isLayered()) + { + throw new AVMException("Not in a layered context: " + path); + } + dir.turnPrimary(lPath); + } + + /** + * Change the indirection of a layered directory. + * @param path The path to the layered directory. + * @param target The target indirection to set. + */ + public void retargetLayeredDirectory(String path, String target) + { + Lookup lPath = lookupDirectory(-1, path); +// lPath.acquireLocks(); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!lPath.isLayered()) + { + throw new AVMException("Not in a layered context: " + path); + } + dir.retarget(lPath, target); + } + + /** + * Set the name of this repository. Hibernate. + * @param name + */ + protected void setName(String name) + { + fName = name; + } + + /** + * Get the name of this Repository. + * @return The name. + */ + public String getName() + { + return fName; + } + + /** + * Set the next version id. + * @param nextVersionID + */ + protected void setNextVersionID(int nextVersionID) + { + fNextVersionID = nextVersionID; + } + + /** + * Get the next version id. + * @return The next version id. + */ + public int getNextVersionID() + { + return fNextVersionID; + } + + /** + * Set the root directory. Hibernate. + * @param root + */ + protected void setRoot(DirectoryNode root) + { + fRoot = root; + } + + /** + * Get the root directory. + * @return The root directory. + */ + public DirectoryNode getRoot() + { + return fRoot; + } + + /** + * Set the version (for concurrency control). Hibernate. + * @param vers + */ + protected void setVers(long vers) + { + fVers = vers; + } + + /** + * Get the version (for concurrency control). Hibernate. + * @return The version. + */ + protected long getVers() + { + return fVers; + } + + /** + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof Repository)) + { + return false; + } + return fName.equals(((Repository)obj).getName()); + } + + /** + * @return + */ + @Override + public int hashCode() + { + // TODO Auto-generated method stub + return super.hashCode(); + } + + /** + * Purge all nodes reachable only via this version and repostory. + * @param version + */ + @SuppressWarnings("unchecked") + public void purgeVersion(int version) + { + if (version == 0) + { + throw new AVMBadArgumentException("Cannot purge initial version"); + } + Query query = fSuper.getSession().getNamedQuery("VersionRoot.VersionByID"); + query.setEntity("rep", this); + query.setInteger("version", version); + VersionRoot vRoot = (VersionRoot)query.uniqueResult(); + AVMNode root = vRoot.getRoot(); + root.setIsRoot(false); + fSuper.getSession().delete(vRoot); + if (root.equals(fRoot)) + { + // We have to set a new current root. + fSuper.getSession().flush(); + query = fSuper.getSession().createQuery("select max(vr.versionID) from VersionRootImpl vr"); + int latest = (Integer)query.uniqueResult(); + query = fSuper.getSession().getNamedQuery("VersionRoot.VersionByID"); + query.setEntity("rep", this); + query.setInteger("version", latest); + vRoot = (VersionRoot)query.uniqueResult(); + fRoot = vRoot.getRoot(); + } + query = fSuper.getSession().getNamedQuery("FindOrphans"); + Iterator iter = (Iterator)query.iterate(); + while (iter.hasNext()) + { + AVMNode node = iter.next(); + System.err.println(node.getId()); + } + } +} diff --git a/source/java/org/alfresco/repo/avm/SuperRepository.java b/source/java/org/alfresco/repo/avm/SuperRepository.java index 9b13caa799..50cfd83d9b 100644 --- a/source/java/org/alfresco/repo/avm/SuperRepository.java +++ b/source/java/org/alfresco/repo/avm/SuperRepository.java @@ -19,234 +19,764 @@ package org.alfresco.repo.avm; import java.io.InputStream; import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; +import org.hibernate.LockMode; +import org.hibernate.Query; import org.hibernate.Session; /** - * A SuperRepository is responsible for the high level implemenation of all - * operations on Repositories. It is responsible for issuing Node ids, branch ids, - * and layer ids. Repositories themselves are responsible for issuing version ids. - * Paths in super repositories are of the form "repositoryname:a/b/c/d". + * This or Repository are + * the implementors of the operations specified by AVMService. * @author britt */ -public interface SuperRepository +class SuperRepository { - // Modify operations. + /** + * The single instance of SuperRepository. + */ + private static SuperRepository fgInstance; /** - * Create a new file. - * @param path The path to create the file in. - * @param name The name to give the new file. + * The Hibernate Session associated with the current operation. */ - public void createFile(String path, String name); + private ThreadLocal fSession; /** - * Create a new Directory. - * @param path The path to create the directory in. - * @param name The name to give the new directory. + * The current lookup count. */ - public void createDirectory(String path, String name); + private ThreadLocal fLookupCount; /** - * Create a new LayeredDirectory. - * @param srcPath The source path that the LayeredDirectory refers to. - * @param dstPath The path to create the new LayeredDirectory in. - * @param name The name to give the new LayeredDirectory. + * The node id issuer. */ - public void createLayeredDirectory(String srcPath, String dstPath, String name); + private Issuer fNodeIssuer; /** - * Create a new LayeredFile. - * @param srcPath The source path that the LayeredFile refers to. - * @param dstPath The path to create the new LayeredFile in. - * @param name The name to give the new LayeredFile. + * The content id issuer; */ - public void createLayeredFile(String srcPath, String dstPath, String name); + private Issuer fContentIssuer; /** - * Create a new Repository. - * @param name The name to give the repository. + * The layer id issuer. */ - public void createRepository(String name); - - /** - * Create a branch. - * @param version The version to look under. - * @param srcPath The path to the source node for the branch. - * @param dstPath The directory to put the branch in. - * @param name The name for the new branch. - */ - public void createBranch(int version, String srcPath, String dstPath, String name); - - // Modify operations. - - /** - * Get an OutputStream to a file. - * @param path The path to the file. - * @return An OutputStream. - */ - public OutputStream getOutputStream(String path); - - /** - * Rename a node. - * @param srcPath The source path. - * @param srcName The name of the node to rename. - * @param dstPath The destination directory for the rename. - * @param dstName The name to give the renamed node. - */ - public void rename(String srcPath, String srcName, String dstPath, String dstName); - - /** - * Slide a directory in a layer to another location in the same - * layer uncovering what was originally shadowed. - * @param srcPath The source path. - * @param srcName The name of the directory to slide. - * @param dstPath The destination directory for the slide. - * @param dstName The name of the directory after sliding. - */ - public void slide(String srcPath, String srcName, String dstPath, String dstName); + private Issuer fLayerIssuer; /** - * Create a snapshot of the given repositories. - * @param repositories A List of Repository names. + * The file storage directory. */ - public void createSnapshot(List repositories); + 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(); + fLookupCount = new ThreadLocal(); + 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 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 repsository. + * @param repository The name of the repository. */ - public void createSnapshot(String repository); - - // Different flavors of deletions. - + public void createSnapshot(String repository) + { + Repository repo = getRepositoryByName(repository); + fSession.get().lock(repo, LockMode.UPGRADE); + repo.createSnapshot(); + } + /** - * Delete a node. + * 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); - - /** - * Purge a repository. - * @param name - */ - public void destroyRepository(String name); - - /** - * Purge a version in a repository. - * @param name The name of the repository. - * @param version The id of the version to purge. - */ - public void purgeVersion(String name, int version); + 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); + } - // Read operations. + /** + * 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 vRoots = (List)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 iter = (Iterator)query.iterate(); + while (iter.hasNext()) + { + AVMNode node = iter.next(); + node.setRepository(null); + } + fSession.get().flush(); + fSession.get().delete(rep); + query = fSession.get().getNamedQuery("FindOrphans"); + List nodes = (List)query.list(); + for (AVMNode node : nodes) + { + System.err.println(node.getId()); + } + } /** - * Get an InputStream from a File. + * 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); - + 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 the listing for a directory. - * @param version The version to get a listing of. + * 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 getListing(int version, String path); - - /** - * Get a listing of all repository names. - * @return A List of repository names. - */ - public List getRepositoryNames(); + public List 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 Set of version IDs for a given Repository. + * Get a directory listing from a directory node descriptor. + * @param dir The directory node descriptor. + * @return + */ + public Map 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 getRepositoryNames() + { + Query query = fSession.get().createQuery("select r.name from RepositoryImpl r"); + return (List)query.list(); + } + + /** + * Get all version ids for a given repository. * @param name The name of the repository. - * @return A Set of IDs. + * @return A Set will all the version ids. */ - public Set getRepositoryVersions(String name); + public Set getRepositoryVersions(String name) + { + Repository rep = getRepositoryByName(name); + fSession.get().lock(rep, LockMode.READ); + return rep.getVersions(); + } + + /** + * 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(); + } /** - * Issue a unique identifier for a new node. - * @return A new identifier. + * Get the (thread local) Hibernate session. + * @return The Session. */ - public long issueID(); - - /** - * Issue an ID for content. - * @return A new content ID. - */ - public long issueContentID(); - - /** - * Issue an ID for the next layer. - * @return The layer id. - */ - public long issueLayerID(); - - /** - * Get the current Hibernate Session. - * @return The Hibernate Session object. - */ - public Session getSession(); - - /** - * Return an OutputStream for the content object of the given id. This - * creates the directory path if needed. - * @param path The path to the file. - * @return An OutputStream. - */ - public OutputStream createContentOutputStream(String path); - - /** - * Gets an input stream from the content with the given id. - * @param version The version id. - * @param path The path to the file. - * @return An InputStream. - */ - public InputStream getContentInputStream(int version, String path); - - /** - * Get the latest version id for a given repository. - * @param name The name of the repository. - * @return The latest version id for the given repository. - */ - public int getLatestVersionID(String name); - + 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); + 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 lookup object for a path. + * 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 full path. - * @return The Lookup. + * @param path The path to lookup. + * @return A lookup object. */ - public Lookup lookup(int version, String path); + 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]); + } /** - * Get a lookup object for a path. Directory only. + * 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 full path. - * @return The Lookup. + * @param path The path to lookup. + * @return A lookup object. */ - public Lookup lookupDirectory(int version, String path); + 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; + } /** - * Get the root directory in which file data is stored. - * @return The root directory of storage. + * Make a directory into a primary indirection. + * @param path The full path. */ - public String getStorageRoot(); + 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; + } } diff --git a/source/java/org/alfresco/repo/avm/VersionRoot.java b/source/java/org/alfresco/repo/avm/VersionRoot.java new file mode 100644 index 0000000000..73b8921a60 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/VersionRoot.java @@ -0,0 +1,86 @@ +/* + * 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; + +/** + * Represents a single version root. + * @author britt + */ +public interface VersionRoot +{ + /** + * @return the createDate + */ + public long getCreateDate(); + + /** + * @param createDate the createDate to set + */ + public void setCreateDate(long createDate); + + /** + * @return the creator + */ + public String getCreator(); + + /** + * @param creator the creator to set + */ + public void setCreator(String creator); + + /** + * @return the id + */ + public long getId(); + + /** + * @param id the id to set + */ + public void setId(long id); + + /** + * @return the repository + */ + public Repository getRepository(); + + /** + * @param repository the repository to set + */ + public void setRepository(Repository repository); + + /** + * @return the root + */ + public DirectoryNode getRoot(); + + /** + * @param root the root to set + */ + public void setRoot(DirectoryNode root); + + /** + * Set the version id. + * @param versionID + */ + public void setVersionID(int versionID); + + /** + * Get the version id. + * @return The version id. + */ + public int getVersionID(); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/VersionRootImpl.java b/source/java/org/alfresco/repo/avm/VersionRootImpl.java new file mode 100644 index 0000000000..3ffca25840 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/VersionRootImpl.java @@ -0,0 +1,188 @@ +/* + * 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.Serializable; + + +/** + * Hold a single version root. + * @author britt + */ +public class VersionRootImpl implements VersionRoot, Serializable +{ + static final long serialVersionUID = 8826954538210455917L; + + /** + * The object id + */ + private Long fID; + + /** + * The version id. + */ + private int fVersionID; + + /** + * The creation date. + */ + private long fCreateDate; + + /** + * The creator. + */ + private String fCreator; + + /** + * The Repository. + */ + private Repository fRepository; + + /** + * The root node. + */ + private DirectoryNode fRoot; + + /** + * A default constructor. + */ + public VersionRootImpl() + { + } + + /** + * Rich constructor. + * @param repository + * @param root + * @param versionID + * @param createDate + * @param creator + */ + public VersionRootImpl(Repository repository, + DirectoryNode root, + int versionID, + long createDate, + String creator) + { + fRepository = repository; + fRoot = root; + fVersionID = versionID; + fCreateDate = createDate; + fCreator = creator; + } + + public long getCreateDate() + { + return fCreateDate; + } + + public void setCreateDate(long createDate) + { + fCreateDate = createDate; + } + + public String getCreator() + { + return fCreator; + } + + public void setCreator(String creator) + { + fCreator = creator; + } + + public long getId() + { + return fID; + } + + public void setId(long id) + { + fID = id; + } + + public Repository getRepository() + { + return fRepository; + } + + public void setRepository(Repository repository) + { + fRepository = repository; + } + + public DirectoryNode getRoot() + { + return fRoot; + } + + public void setRoot(DirectoryNode root) + { + fRoot = root; + } + + /** + * Set the versionID. + * @param versionID + */ + public void setVersionID(int versionID) + { + fVersionID = versionID; + } + + /** + * Get the version id. + * @return The version id. + */ + public int getVersionID() + { + return fVersionID; + } + + /** + * Check equality. Based on Repository equality and version id equality. + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof VersionRoot)) + { + return false; + } + VersionRoot other = (VersionRoot)obj; + return fRepository.equals(other.getRepository()) + && fVersionID == other.getVersionID(); + } + + /** + * Generate a hash code. + * @return The hash code. + */ + @Override + public int hashCode() + { + return fRepository.hashCode() + fVersionID; + } +} + diff --git a/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml b/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml index 84f8497c57..f26acb1f31 100644 --- a/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml +++ b/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml @@ -1,50 +1,51 @@ - + + name="AVMNodeImpl" + proxy="AVMNode" + optimistic-lock="version" + lazy="true"> - + - + - + + class="AVMNodeImpl"/> + class="AVMNodeImpl"/> + class="DirectoryNodeImpl"/> + class="RepositoryImpl"/> - - - - + @@ -52,19 +53,19 @@ + - - - + @@ -72,74 +73,36 @@ it points at (true) or inheriting what it points at from its container (false). --> + column="primary_indirection" type="boolean"/> - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - + name="PlainFileNodeImpl" proxy="PlainFileNode" lazy="true"> + class="FileContentImpl" fetch="join" cascade="save-update"> - + @@ -147,7 +110,7 @@ - @@ -157,51 +120,128 @@ scheme. --> - - - - - - - - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/avm/hibernate/HibernateTxn.java b/source/java/org/alfresco/repo/avm/hibernate/HibernateTxn.java index a52507e3c1..02ec92e0bc 100644 --- a/source/java/org/alfresco/repo/avm/hibernate/HibernateTxn.java +++ b/source/java/org/alfresco/repo/avm/hibernate/HibernateTxn.java @@ -17,10 +17,16 @@ package org.alfresco.repo.avm.hibernate; * License. */ +import java.util.Random; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.repo.avm.AVMException; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.StaleStateException; import org.hibernate.Transaction; +import org.hibernate.exception.GenericJDBCException; /** * Helper for DAOs. @@ -33,6 +39,16 @@ public class HibernateTxn */ private SessionFactory fSessionFactory; + /** + * The random number generator for inter-retry sleep. + */ + private Random fRandom; + + /** + * The BFL. + */ + private ReentrantReadWriteLock fLock; + /** * Make one up. * @param sessionFactory The SessionFactory. @@ -40,52 +56,110 @@ public class HibernateTxn public HibernateTxn(SessionFactory sessionFactory) { fSessionFactory = sessionFactory; + fRandom = new Random(); + fLock = new ReentrantReadWriteLock(true); // Make the lock fair. } /** * Perform a set of operations under a single Hibernate transaction. + * Keep trying if the operation fails because of a concurrency issue. * @param callback The worker. + * @param write Whether this is a write operation. * @return Whether the operation finished with a commit. */ - public boolean perform(HibernateTxnCallback callback) + public void perform(HibernateTxnCallback callback, boolean write) { Session sess = null; Transaction txn = null; - try + while (true) { - sess = fSessionFactory.openSession(); - txn = sess.beginTransaction(); - callback.perform(sess); - txn.commit(); - return true; - } - catch (Throwable t) - { - t.printStackTrace(System.err); - if (txn != null) + try { - try + /* + if (write) { - txn.rollback(); + fLock.writeLock().lock(); } - catch (HibernateException he) + else { - // Do nothing. + fLock.readLock().lock(); } + */ + sess = fSessionFactory.openSession(); + txn = sess.beginTransaction(); + callback.perform(sess); + txn.commit(); + return; } - return false; - } - finally - { - if (sess != null) + catch (Throwable t) { - try + // TODO Add appropriate logging. + if (txn != null) { - sess.close(); + try + { + txn.rollback(); + } + catch (HibernateException he) + { + // Do nothing. + } + // If we've lost a race or we've deadlocked, retry. + if (t instanceof StaleStateException || + t instanceof GenericJDBCException) + { + if (t instanceof StaleStateException) + { + System.err.println("Lost Race"); + continue; + } + System.err.println("Deadlock"); + try + { + long interval; + synchronized (fRandom) + { + interval = fRandom.nextInt(1000); + } + Thread.sleep(interval); + } + catch (InterruptedException ie) + { + // Do nothing. + } + continue; + } } - catch (HibernateException he) + if (t instanceof AVMException) { - // Do nothing. + throw (AVMException)t; + } + // TODO Crack t into more useful exception types. + // Otherwise nothing we can do except throw. + throw new AVMException("Unrecoverable error", t); + } + finally + { + /* + if (write) + { + fLock.writeLock().unlock(); + } + else + { + fLock.readLock().unlock(); + } + */ + if (sess != null) + { + try + { + sess.close(); + } + catch (HibernateException he) + { + // Do nothing. + } } } } diff --git a/source/java/org/alfresco/repo/avm/impl/AVMServiceImpl.java b/source/java/org/alfresco/repo/avm/impl/AVMServiceImpl.java deleted file mode 100644 index 6969845b15..0000000000 --- a/source/java/org/alfresco/repo/avm/impl/AVMServiceImpl.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * 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.impl; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; -import java.util.Set; - -import org.alfresco.repo.avm.AVMService; -import org.alfresco.repo.avm.FolderEntry; -import org.alfresco.repo.avm.Lookup; -import org.alfresco.repo.avm.SuperRepository; -import org.alfresco.repo.avm.hibernate.HibernateHelper; -import org.alfresco.repo.avm.hibernate.HibernateTxn; -import org.alfresco.repo.avm.hibernate.HibernateTxnCallback; -import org.alfresco.repo.avm.hibernate.Issuer; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.tool.hbm2ddl.SchemaExport; - -/** - * Implements the AVMService. Stub. - * @author britt - */ -public class AVMServiceImpl implements AVMService -{ - /** - * The Hibernate SessionFactory. - */ - private SessionFactory fSessionFactory; - - /** - * The HibernateTxn. - */ - private HibernateTxn fTransaction; - - /** - * The SuperRepository for each service thread. - */ - private ThreadLocal fSuperRepository; - - /** - * The storage directory. - */ - private String fStorage; - - /** - * Basic constructor for the service. - * @param createTables Flag for whether tables should be created. - */ - public AVMServiceImpl() - { - fSuperRepository = new ThreadLocal(); - fSessionFactory = HibernateHelper.GetSessionFactory(); - fTransaction = new HibernateTxn(fSessionFactory); - } - - /** - * Final initialization of the service. Must be called only on a - * fully initialized instance. - * @param createTables Whether we should create tables, and a default - * repository. - */ - public void init(boolean createTables) - { - if (createTables) - { - SchemaExport se = new SchemaExport(HibernateHelper.GetConfiguration()); - se.drop(false, true); - se.create(false, true); - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - new Issuer("node", 0L, session); - new Issuer("content", 0L, session); - new Issuer("branch", 0L, session); - new Issuer("layer", 0L, session); - } - }; - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - createRepository("main"); - } - } - - /** - * Set the location of file storage. - * @param storage - */ - public void setStorage(String storage) - { - fStorage = storage; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#getFileInputStream(int, java.lang.String) - */ - public InputStream getFileInputStream(final int version, final String path) - { - class HTxnCallback implements HibernateTxnCallback - { - public InputStream in = null; - - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - in = fSuperRepository.get().getInputStream(version, path); - } - }; - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - return doit.in; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#getFileOutputStream(java.lang.String) - */ - public OutputStream getFileOutputStream(final String path) - { - class HTxnCallback implements HibernateTxnCallback - { - public OutputStream out = null; - - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - out = fSuperRepository.get().getOutputStream(path); - } - }; - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - return doit.out; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#getFolderListing(int, java.lang.String) - */ - public List getDirectoryListing(final int version, final String path) - { - class HTxnCallback implements HibernateTxnCallback - { - public List listing; - - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - listing = fSuperRepository.get().getListing(version, path); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - return doit.listing; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createFile(java.lang.String, java.lang.String) - */ - public void createFile(final String path, final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createFile(path, name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createFolder(java.lang.String, java.lang.String) - */ - public void createDirectory(final String path, final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createDirectory(path, name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createLayeredFile(java.lang.String, java.lang.String, java.lang.String) - */ - public void createLayeredFile(final String srcPath, final String parent, final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createLayeredFile(srcPath, parent, name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createLayeredFolder(java.lang.String, java.lang.String, java.lang.String) - */ - public void createLayeredDirectory(final String srcPath, final String parent, final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createLayeredDirectory(srcPath, parent, name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createRepository(java.lang.String) - */ - public void createRepository(final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createRepository(name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createBranch(int, java.lang.String, java.lang.String, java.lang.String) - */ - public void createBranch(final int version, final String srcPath, final String dstPath, - final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createBranch(version, srcPath, dstPath, name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#removeNode(java.lang.String, java.lang.String) - */ - public void removeNode(final String parent, final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().remove(parent, name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#rename(java.lang.String, java.lang.String, java.lang.String, java.lang.String) - */ - public void rename(final String srcParent, final String srcName, final String dstParent, - final String dstName) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().rename(srcParent, srcName, dstParent, dstName); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#slide(java.lang.String, java.lang.String, java.lang.String, java.lang.String) - */ - public void slide(final String srcParent, final String srcName, final String dstParent, - final String dstName) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().slide(srcParent, srcName, dstParent, dstName); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#getLatestVersionID(java.lang.String) - */ - public int getLatestVersionID(final String repName) - { - class HTxnCallback implements HibernateTxnCallback - { - public int latestVersionID; - - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - latestVersionID = fSuperRepository.get().getLatestVersionID(repName); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - return doit.latestVersionID; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createSnapshot(java.util.List) - */ - public void createSnapshot(final List repositories) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createSnapshot(repositories); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#createSnapshot(java.lang.String) - */ - public void createSnapshot(final String repository) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().createSnapshot(repository); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#lookup(int, java.lang.String) - */ - public Lookup lookup(final int version, final String path) - { - class HTxnCallback implements HibernateTxnCallback - { - public Lookup lookup; - - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - lookup = fSuperRepository.get().lookup(version, path); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - return doit.lookup; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#destroyRepository(java.lang.String) - */ - public void destroyRepository(final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().destroyRepository(name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#purgeVersion(int, java.lang.String) - */ - public void purgeVersion(final int version, final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - fSuperRepository.get().purgeVersion(name, version); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#getIndirectionPath(java.lang.String) - */ - public String getIndirectionPath(final int version, final String path) - { - class HTxnCallback implements HibernateTxnCallback - { - public String indirectionPath; - - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - indirectionPath = fSuperRepository.get().getIndirectionPath(version, path); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - return doit.indirectionPath; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#getRepositoryVersions(java.lang.String) - */ - public Set getRepositoryVersions(final String name) - { - class HTxnCallback implements HibernateTxnCallback - { - public Set versions; - - public void perform(Session session) - { - fSuperRepository.set(new SuperRepositoryImpl(session, fStorage)); - versions = fSuperRepository.get().getRepositoryVersions(name); - } - } - HTxnCallback doit = new HTxnCallback(); - fTransaction.perform(doit); - return doit.versions; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMService#retargetLayeredFolder(java.lang.String, java.lang.String) - */ - public void retargetLayeredFolder(String path, String target) - { - // TODO Auto-generated method stub - - } -} diff --git a/source/java/org/alfresco/repo/avm/impl/RepositoryImpl.java b/source/java/org/alfresco/repo/avm/impl/RepositoryImpl.java deleted file mode 100644 index 5384860738..0000000000 --- a/source/java/org/alfresco/repo/avm/impl/RepositoryImpl.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * 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.impl; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.avm.AVMNode; -import org.alfresco.repo.avm.AVMNodeFactory; -import org.alfresco.repo.avm.DirectoryNode; -import org.alfresco.repo.avm.FileContent; -import org.alfresco.repo.avm.FileNode; -import org.alfresco.repo.avm.FolderEntry; -import org.alfresco.repo.avm.Layered; -import org.alfresco.repo.avm.LayeredDirectoryNode; -import org.alfresco.repo.avm.LayeredFileNode; -import org.alfresco.repo.avm.Lookup; -import org.alfresco.repo.avm.PlainDirectoryNode; -import org.alfresco.repo.avm.PlainFileNode; -import org.alfresco.repo.avm.Repository; -import org.alfresco.repo.avm.SuperRepository; -import org.alfresco.repo.avm.hibernate.AVMNodeBean; -import org.alfresco.repo.avm.hibernate.BasicAttributesBean; -import org.alfresco.repo.avm.hibernate.BasicAttributesBeanImpl; -import org.alfresco.repo.avm.hibernate.DirectoryEntry; -import org.alfresco.repo.avm.hibernate.DirectoryNodeBean; -import org.alfresco.repo.avm.hibernate.PlainDirectoryNodeBean; -import org.alfresco.repo.avm.hibernate.PlainDirectoryNodeBeanImpl; -import org.alfresco.repo.avm.hibernate.RepositoryBean; -import org.alfresco.repo.avm.hibernate.RepositoryBeanImpl; - -/** - * @author britt - * - */ -public class RepositoryImpl implements Repository -{ - /** - * The data bean. - */ - private RepositoryBean fData; - - /** - * The super repository. - */ - private SuperRepository fSuper; - - /** - * Make a brand new repository. - * @param superRepo The SuperRepository. - * @param name The name of the Repository. - */ - public RepositoryImpl(SuperRepository superRepo, String name) - { - fSuper = superRepo; - fData = new RepositoryBeanImpl(name, null); - fSuper.getSession().save(fData); - long time = System.currentTimeMillis(); - // TODO Obviously we have to figure out how to get users from context. - BasicAttributesBean attrs = new BasicAttributesBeanImpl("britt", - "britt", - "britt", - time, - time, - time); - PlainDirectoryNodeBean rootBean = - new PlainDirectoryNodeBeanImpl(fSuper.issueID(), - fData.getNextVersionID(), - 0L, - null, - null, - null, - fData, - attrs, - true // is root - ); - fSuper.getSession().save(rootBean); - fData.setRoot(rootBean); - fData.getRoots().put(fData.getNextVersionID(), rootBean); - fData.setNextVersionID(fData.getNextVersionID()); - fSuper.getSession().save(fData); - } - - /** - * Make one from a data bean. - * @param data The data. - */ - public RepositoryImpl(SuperRepository superRepo, RepositoryBean data) - { - fData = data; - fSuper = superRepo; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#setNew(org.alfresco.repo.avm.AVMNode) - */ - public void setNew(AVMNode node) - { - fData.getNewNodes().add(node.getDataBean()); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getLatestVersion() - */ - public int getLatestVersion() - { - return fData.getNextVersionID(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#setNewRoot(org.alfresco.repo.avm.DirectoryNode) - */ - public void setNewRoot(DirectoryNode root) - { - fData.setRoot((DirectoryNodeBean)root.getDataBean()); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#createSnapshot() - */ - public void createSnapshot() - { - // Walk through the new nodes and mark them not new. - for (AVMNodeBean newBean : fData.getNewNodes()) - { - newBean.setIsNew(false); - } - // Clear out the new nodes. - fData.getNewNodes().clear(); - // Add the current root to the root history. - fData.getRoots().put(fData.getNextVersionID(), fData.getRoot()); - // Increment the version id. - fData.setNextVersionID(fData.getNextVersionID() + 1); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#createDirectory(java.lang.String, java.lang.String) - */ - public void createDirectory(String path, String name) - { - Lookup lPath = lookupDirectory(-1, path); - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (dir.lookupChild(lPath, name, -1) != null) - { - throw new AlfrescoRuntimeException("Child exists: " + name); - } - DirectoryNode newDir = null; - if (lPath.isLayered()) // Creating a directory in a layered context creates - // a LayeredDirectoryNode that gets its indirection from - // its parent. - { - newDir = new LayeredDirectoryNode((String)null, this); - ((LayeredDirectoryNode)newDir).setPrimaryIndirection(false); - } - else - { - newDir = new PlainDirectoryNode(this); - } - newDir.setVersion(getLatestVersion() + 1); - this.setNew(newDir); - dir.addChild(name, newDir, lPath); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#createLayeredDirectory(java.lang.String, java.lang.String, java.lang.String) - */ - public void createLayeredDirectory(String srcPath, String dstPath, - String name) - { - Lookup lPath = lookupDirectory(-1, dstPath); - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (dir.lookupChild(lPath, name, -1) != null) - { - throw new AlfrescoRuntimeException("Child exists: " + name); - } - LayeredDirectoryNode newDir = - new LayeredDirectoryNode(srcPath, this); - if (lPath.isLayered()) - { - // When a layered directory is made inside of a layered context, - // it gets its layer id from the topmost layer in its lookup - // path. - LayeredDirectoryNode top = lPath.getTopLayer(); - newDir.setLayerID(top.getLayerID()); - } - else - { - // Otherwise we issue a brand new layer id. - newDir.setLayerID(fSuper.issueLayerID()); - } - dir.addChild(name, newDir, lPath); - newDir.setVersion(getLatestVersion() + 1); - setNew(newDir); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#createFile(java.lang.String, java.lang.String) - */ - public void createFile(String path, String name) - { - Lookup lPath = lookupDirectory(-1, path); - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (dir.lookupChild(lPath, name, -1) != null) - { - throw new AlfrescoRuntimeException("Child exists: " + name); - } - PlainFileNode file = new PlainFileNode(this); - file.setVersion(getLatestVersion() + 1); - setNew(file); - dir.addChild(name, file, lPath); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#createLayeredFile(java.lang.String, java.lang.String, java.lang.String) - */ - public void createLayeredFile(String srcPath, String dstPath, String name) - { - Lookup lPath = lookupDirectory(-1, dstPath); - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (dir.lookupChild(lPath, name, -1) != null) - { - throw new AlfrescoRuntimeException("Child exists: " + name); - } - // TODO Reexamine decision to not check validity of srcPath. - LayeredFileNode newFile = - new LayeredFileNode(srcPath, this); - dir.addChild(name, newFile, lPath); - newFile.setVersion(getLatestVersion() + 1); - setNew(newFile); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getInputStream(int, java.lang.String) - */ - public InputStream getInputStream(int version, String path) - { - Lookup lPath = lookup(version, path); - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AlfrescoRuntimeException("Not a file: " + path + " r " + version); - } - FileNode file = (FileNode)node; - FileContent content = file.getContentForRead(version, this); - return content.getInputStream(fSuper); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getListing(int, java.lang.String) - */ - public List getListing(int version, String path) - { - Lookup lPath = lookupDirectory(version, path); - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - Map listing = dir.getListing(lPath, version); - ArrayList results = new ArrayList(); - for (String name : listing.keySet()) - { - FolderEntry item = new FolderEntry(); - item.setName(name); - item.setType(listing.get(name).getType()); - results.add(item); - } - return results; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getOutputStream(java.lang.String) - */ - public OutputStream getOutputStream(String path) - { - Lookup lPath = lookup(-1, path); - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AlfrescoRuntimeException("Not a file: " + path); - } - FileNode file = (FileNode)node; - file = (FileNode)file.copyOnWrite(lPath); - FileContent content = file.getContentForWrite(this); - return content.getOutputStream(fSuper); // TODO Do we really need fSuper? - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#removeNode(java.lang.String, java.lang.String) - */ - public void removeNode(String path, String name) - { - Lookup lPath = lookupDirectory(-1, path); - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (dir.lookupChild(lPath, name, -1) == null) - { - throw new AlfrescoRuntimeException("Does not exist: " + name); - } - dir.removeChild(name, lPath); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#slide(java.lang.String, java.lang.String, java.lang.String, java.lang.String) - */ - public void slide(String srcPath, String srcName, String dstPath, - String dstName) - { - Lookup sPath = lookup(-1, srcPath); - if (!sPath.isLayered()) - { - throw new AlfrescoRuntimeException("Slide not allowed from non-layered directory."); - } - AVMNode node = sPath.getCurrentNode(); - if (!(node instanceof LayeredDirectoryNode)) - { - throw new AlfrescoRuntimeException("Not a layered directory: " + srcPath); - } - LayeredDirectoryNode srcDir = (LayeredDirectoryNode)node; - AVMNode srcNode = srcDir.lookupChild(sPath, srcName, -1); - if (srcNode == null) - { - throw new AlfrescoRuntimeException("Not found: " + srcName); - } - if (!(srcNode instanceof LayeredDirectoryNode)) - { - throw new AlfrescoRuntimeException("Not a layered directory:" + srcName); - } - if (!sPath.isInThisLayer() || !srcDir.directlyContains(srcNode)) - { - throw new AlfrescoRuntimeException("Not in this layer: " + srcName); - } - Lookup dPath = lookupDirectory(-1, dstPath); - if (!dPath.isLayered() || !dPath.getTopLayer().equals(sPath.getTopLayer())) - { - throw new AlfrescoRuntimeException("Destination must be in same layer: " + dstPath); - } - DirectoryNode dstDir = (DirectoryNode)dPath.getCurrentNode(); - if (dstDir.lookupChild(dPath, dstName, -1) != null) - { - throw new AlfrescoRuntimeException("Destination exists: " + dstName); - } - // Remove child from src without leaving a ghost. - LayeredDirectoryNode srcDirCopy = - (LayeredDirectoryNode)srcDir.copyOnWrite(sPath); - srcDirCopy.rawRemoveChildNoGhost(srcName); - // Make a new version of source directly to be slid. - LayeredDirectoryNode dstNode = - new LayeredDirectoryNode((LayeredDirectoryNode)srcNode, this); - // Relookup the destination, since the lookup have been invalidated - // by the src copy on write. - dPath = lookup(-1, dstPath); - dstDir = (DirectoryNode)dPath.getCurrentNode(); - dstDir.addChild(dstName, dstNode, dPath); - } - - // TODO This is problematic. As time goes on this returns - // larger and larger data sets. Perhaps what we should do is - // provide methods for getting versions by date range, n most - // recent etc. - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getVersions() - */ - public Set getVersions() - { - return fData.getRoots().keySet(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getDataBean() - */ - public RepositoryBean getDataBean() - { - return fData; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getSuperRepository() - */ - public SuperRepository getSuperRepository() - { - return fSuper; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#lookup(int, java.lang.String) - */ - public Lookup lookup(int version, String path) - { - // Make up a Lookup to hold the results. - Lookup result = new Lookup(this, fData.getName()); // TODO redundant. - if (path.length() == 0) - { - throw new AlfrescoRuntimeException("Invalid path: " + path); - } - if (path.length() > 1) - { - path = path.substring(1); - } - String[] pathElements = path.split("/"); - // Grab the root node to start the lookup. - DirectoryNode dir = null; - // Versions less than 0 mean get current. - if (version < 0) - { - dir = (DirectoryNode)AVMNodeFactory.CreateFromBean(fData.getRoot()); - } - else - { - AVMNodeBean bean = fData.getRoots().get(version); - if (bean == null) - { - throw new AlfrescoRuntimeException("Invalid version: " + version); - } - dir = (DirectoryNode)AVMNodeFactory.CreateFromBean(bean); - } - // Add an entry for the root. - result.add(dir, ""); - if (pathElements.length == 0) - { - return result; - } - // Now look up each path element in sequence up to one - // before the end. - for (int i = 0; i < pathElements.length - 1; i++) - { - AVMNode child = dir.lookupChild(result, pathElements[i], version); - if (child == null) - { - throw new AlfrescoRuntimeException("Not found: " + pathElements[i]); - } - // Every element that is not the last needs to be a directory. - if (!(child instanceof DirectoryNode)) - { - throw new AlfrescoRuntimeException("Not a directory: " + pathElements[i]); - } - dir = (DirectoryNode)child; - result.add(dir, pathElements[i]); - } - // Now look up the last element. - AVMNode child = dir.lookupChild(result, pathElements[pathElements.length - 1], version); - if (child == null) - { - throw new AlfrescoRuntimeException("Not found: " + pathElements[pathElements.length - 1]); - } - result.add(child, pathElements[pathElements.length - 1]); - return result; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#lookupDirectory(int, java.lang.String) - */ - public Lookup lookupDirectory(int version, String path) - { - // Just do a regular lookup and assert that the last element - // is a directory. - Lookup lPath = lookup(version, path); - if (!(lPath.getCurrentNode() instanceof DirectoryNode)) - { - throw new AlfrescoRuntimeException("Not a directory: " + path); - } - return lPath; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.Repository#getIndirectionPath(int, java.lang.String) - */ - public String getIndirectionPath(int version, String path) - { - Lookup lPath = lookup(version, path); - AVMNode node = lPath.getCurrentNode(); - if (node instanceof Layered) - { - return ((Layered)node).getUnderlying(lPath); - } - throw new AlfrescoRuntimeException("Not a layered node: " + path); - } -} diff --git a/source/java/org/alfresco/repo/avm/impl/SuperRepositoryImpl.java b/source/java/org/alfresco/repo/avm/impl/SuperRepositoryImpl.java deleted file mode 100644 index b506102b4e..0000000000 --- a/source/java/org/alfresco/repo/avm/impl/SuperRepositoryImpl.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * 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.impl; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.avm.AVMNode; -import org.alfresco.repo.avm.DirectoryNode; -import org.alfresco.repo.avm.FileNode; -import org.alfresco.repo.avm.FolderEntry; -import org.alfresco.repo.avm.LayeredDirectoryNode; -import org.alfresco.repo.avm.LayeredFileNode; -import org.alfresco.repo.avm.Lookup; -import org.alfresco.repo.avm.PlainDirectoryNode; -import org.alfresco.repo.avm.PlainFileNode; -import org.alfresco.repo.avm.Repository; -import org.alfresco.repo.avm.SuperRepository; -import org.alfresco.repo.avm.hibernate.Issuer; -import org.alfresco.repo.avm.hibernate.RepositoryBean; -import org.alfresco.repo.avm.hibernate.RepositoryBeanImpl; -import org.hibernate.Query; -import org.hibernate.Session; - -/** - * Implementation of a SuperRepository. This is a per thread - * class. - * @author britt - */ -public class SuperRepositoryImpl implements SuperRepository -{ - /** - * The Hibernate Session associated with the current operation. - */ - private Session fSession; - - /** - * The node id issuer. - */ - private Issuer fNodeIssuer; - - /** - * The content id issuer; - */ - private Issuer fContentIssuer; - - /** - * The branch id issuer. - */ - private Issuer fBranchIssuer; - - /** - * The layer id issuer. - */ - private Issuer fLayerIssuer; - - /** - * The file storage directory. - */ - private String fStorage; - - // TODO Issuers are handled in a repugnant manner here. Something better - // would be nice. - /** - * Make a new one, initialized with the session. - * @param session The session for this operation. - * @param storage Where file data gets stored. - */ - public SuperRepositoryImpl(Session session, String storage) - { - fSession = session; - fStorage = storage; - fNodeIssuer = null; - fContentIssuer = null; - fBranchIssuer = null; - fLayerIssuer = null; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createFile(java.lang.String, java.lang.String) - */ - public void createFile(String path, String name) - { - String[] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - rep.createFile(pathParts[1], name); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createDirectory(java.lang.String, java.lang.String) - */ - public void createDirectory(String path, String name) - { - String[] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - rep.createDirectory(pathParts[1], name); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createLayeredDirectory(java.lang.String, java.lang.String, java.lang.String) - */ - public void createLayeredDirectory(String srcPath, String dstPath, - String name) - { - String[] pathParts = SplitPath(dstPath); - Repository rep = getRepositoryByName(pathParts[0]); - rep.createLayeredDirectory(srcPath, pathParts[1], name); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createLayerdFile(java.lang.String, java.lang.String, java.lang.String) - */ - public void createLayeredFile(String srcPath, String dstPath, String name) - { - String[] pathParts = SplitPath(dstPath); - Repository rep = getRepositoryByName(pathParts[0]); - rep.createLayeredFile(srcPath, pathParts[1], name); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createRepository(java.lang.String) - */ - public void createRepository(String name) - { - // 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. - rep.getDataBean().getRoot().setIsNew(false); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createBranch(int, java.lang.String, java.lang.String, java.lang.String) - */ - public void createBranch(int version, String srcPath, String dstPath, String name) - { - // Lookup the src node. - String [] pathParts = SplitPath(srcPath); - Repository srcRepo = getRepositoryByName(pathParts[0]); - Lookup sPath = srcRepo.lookup(version, pathParts[1]); - // Lookup the destination directory. - pathParts = SplitPath(dstPath); - Repository dstRepo = getRepositoryByName(pathParts[0]); - Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1]); - 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 instanceof PlainDirectoryNode) - { - dstNode = new PlainDirectoryNode((PlainDirectoryNode)srcNode, dstRepo); - } - else if (srcNode instanceof LayeredDirectoryNode) - { - dstNode = - new LayeredDirectoryNode((LayeredDirectoryNode)srcNode, dstRepo); - } - else if (srcNode instanceof LayeredFileNode) - { - dstNode = new LayeredFileNode((LayeredFileNode)srcNode, dstRepo); - } - else // This is a plain file. - { - dstNode = new PlainFileNode((PlainFileNode)srcNode, dstRepo); - } - dstNode.setVersion(dstRepo.getLatestVersion() + 1); - dstRepo.setNew(dstNode); - dstNode.setAncestor(srcNode); - dstNode.setBranchID(issueBranchID()); - dirNode.addChild(name, dstNode, dPath); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getOutputStream(java.lang.String) - */ - public OutputStream getOutputStream(String path) - { - String [] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - return rep.getOutputStream(pathParts[1]); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#rename(java.lang.String, java.lang.String, java.lang.String, java.lang.String) - */ - public void rename(String srcPath, String srcName, String dstPath, - String dstName) - { - // This is about as ugly as it gets. - String [] pathParts = SplitPath(srcPath); - Repository srcRepo = getRepositoryByName(pathParts[0]); - Lookup sPath = srcRepo.lookupDirectory(-1, pathParts[1]); - DirectoryNode srcDir = (DirectoryNode)sPath.getCurrentNode(); - AVMNode srcNode = srcDir.lookupChild(sPath, srcName, -1); - if (srcNode == null) - { - throw new AlfrescoRuntimeException("Not found: " + srcName); - } - pathParts = SplitPath(dstPath); - Repository dstRepo = getRepositoryByName(pathParts[0]); - Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1]); - DirectoryNode dstDir = (DirectoryNode)dPath.getCurrentNode(); - AVMNode dstNode = dstDir.lookupChild(dPath, dstName, -1); - if (dstNode != null) - { - throw new AlfrescoRuntimeException("Node exists: " + dstName); - } - // We've passed the check, so we can go ahead and do the rename. - if (srcNode instanceof PlainDirectoryNode) - { - // 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() == sPath.getTopLayer()) - { - dstNode = new LayeredDirectoryNode((PlainDirectoryNode)srcNode, dstRepo, sPath); - } - else - { - dstNode = new LayeredDirectoryNode((DirectoryNode)srcNode, dstRepo, sPath, srcName); - } - } - else - { - dstNode = new PlainDirectoryNode((PlainDirectoryNode)srcNode, dstRepo); - } - } - else if (srcNode instanceof LayeredDirectoryNode) - { - // 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 instanceof LayeredDirectoryNode && - ((LayeredDirectoryNode)srcDir).directlyContains(srcNode))) - { - // Use the simple 'copy' constructor. - dstNode = - new LayeredDirectoryNode((LayeredDirectoryNode)srcNode, dstRepo); - ((LayeredDirectoryNode)dstNode).setLayerID(((LayeredDirectoryNode)srcNode).getLayerID()); - } - else - { - // The thing we are renaming does not belong to sPath's layer and therefore - // we need to compute the indirection path for this layer after the rename. - dstNode = - new LayeredDirectoryNode((DirectoryNode)srcNode, dstRepo, sPath, srcName); - ((LayeredDirectoryNode)dstNode).setLayerID(issueLayerID()); - } - } - else if (srcNode instanceof LayeredFileNode) - { - if (!sPath.isLayered() || (sPath.isInThisLayer() && - srcDir instanceof LayeredDirectoryNode && - ((LayeredDirectoryNode)srcDir).directlyContains(srcNode))) - { - // Use the simple 'copy' constructor. - dstNode = - new LayeredFileNode((LayeredFileNode)srcNode, dstRepo); - } - else - { - // Calculate the indirection path, because srcNode was not in this layer. - dstNode = - new LayeredFileNode((FileNode)srcNode, dstRepo, sPath, srcName); - } - } - else // This is a plain file node. - { - dstNode = new PlainFileNode((PlainFileNode)srcNode, dstRepo); - } - dstNode.setVersion(dstRepo.getLatestVersion() + 1); - dstRepo.setNew(dstNode); - dstDir.addChild(dstName, dstNode, dPath); - dstNode.setAncestor(srcNode); - pathParts = SplitPath(srcPath); - sPath = srcRepo.lookup(-1, pathParts[1]); - srcDir = (DirectoryNode)sPath.getCurrentNode(); - srcDir.removeChild(srcName, sPath); - } - - // TODO Should we allow cross-repository sliding. Tentatively no, because - // it serves no earthly purpose. God knows we need to trim the combinatorial - // tree of possibilities. - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#slide(java.lang.String, java.lang.String, java.lang.String, java.lang.String) - */ - public void slide(String srcPath, String srcName, String dstPath, - String dstName) - { - String[] srcPathParts = SplitPath(srcPath); - String[] dstPathParts = SplitPath(dstPath); - if (!srcPathParts[0].equals(dstPathParts[0])) - { - throw new AlfrescoRuntimeException("Argument must be in same Repository: " + srcPathParts[0] + "!=" + - dstPathParts[0]); - } - Repository repo = getRepositoryByName(srcPathParts[0]); - repo.slide(srcPathParts[1], srcName, dstPathParts[1], dstName); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createSnapshot(java.util.List) - */ - public void createSnapshot(List repositories) - { - for (String repName : repositories) - { - Repository repo = getRepositoryByName(repName); - repo.createSnapshot(); - } - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createSnapshot(java.lang.String) - */ - public void createSnapshot(String repository) - { - Repository repo = getRepositoryByName(repository); - repo.createSnapshot(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#remove(java.lang.String, java.lang.String) - */ - public void remove(String path, String name) - { - String [] pathParts = SplitPath(path); - Repository repo = getRepositoryByName(pathParts[0]); - repo.removeNode(pathParts[1], name); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#purgeRepository(java.lang.String) - */ - public void purgeRepository(String name) - { - // TODO Leave until later. Need to set up GC thread to handle this. - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#purgeVersion(java.lang.String, int) - */ - public void purgeVersion(String name, int version) - { - // TODO Leave until later. Need to set up GC thread to handle this. - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getInputStream(java.lang.String) - */ - public InputStream getInputStream(int version, String path) - { - String [] pathParts = SplitPath(path); - Repository repo = getRepositoryByName(pathParts[0]); - return repo.getInputStream(version, pathParts[1]); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getListing(java.lang.String) - */ - public List getListing(int version, String path) - { - String [] pathParts = SplitPath(path); - Repository repo = getRepositoryByName(pathParts[0]); - return repo.getListing(version, pathParts[1]); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getRepositoryNames() - */ - @SuppressWarnings("unchecked") - public List getRepositoryNames() - { - Query query = fSession.createQuery("select r.name from RepositoryBeanImpl r"); - return (List)query.list(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getRepositoryVersions(java.lang.String) - */ - public Set getRepositoryVersions(String name) - { - Repository rep = getRepositoryByName(name); - return rep.getVersions(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#issueID() - */ - public long issueID() - { - if (fNodeIssuer == null) - { - fNodeIssuer = (Issuer)fSession.get(Issuer.class, "node"); - } - return fNodeIssuer.issue(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#issueContentID() - */ - public long issueContentID() - { - if (fContentIssuer == null) - { - fContentIssuer = (Issuer)fSession.get(Issuer.class, "content"); - } - return fContentIssuer.issue(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#issueLayerID() - */ - public long issueLayerID() - { - if (fLayerIssuer == null) - { - fLayerIssuer = (Issuer)fSession.get(Issuer.class, "layer"); - } - return fLayerIssuer.issue(); - } - - private long issueBranchID() - { - if (fBranchIssuer == null) - { - fBranchIssuer = (Issuer)fSession.get(Issuer.class, "branch"); - } - return fBranchIssuer.issue(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getSession() - */ - public Session getSession() - { - return fSession; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#createContentOutputStream(int) - */ - public OutputStream createContentOutputStream(String path) - { - String [] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - return rep.getOutputStream(pathParts[1]); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getContentInputStream(int) - */ - public InputStream getContentInputStream(int version, String path) - { - String[] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - return rep.getInputStream(version, pathParts[1]); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#destroyRepository(java.lang.String) - */ - public void destroyRepository(String name) - { - // TODO Auto-generated method stub - // Leave this until we have GC in place. - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getIndirectionPath(int, java.lang.String) - */ - public String getIndirectionPath(int version, String path) - { - String [] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - return rep.getIndirectionPath(version, pathParts[1]); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getLatestVersionID(java.lang.String) - */ - public int getLatestVersionID(String name) - { - Repository rep = getRepositoryByName(name); - return rep.getLatestVersion(); - } - - /** - * Get a repository by name. - * @param name The name of the repository. - * @return The Repository. - */ - private Repository getRepositoryByName(String name) - { -/* - Query query = fSession.createQuery("from RepositoryBeanImpl r where r.name = :name"); - query.setString("name", name); - return new RepositoryImpl(this, (RepositoryBean)query.uniqueResult()); -*/ - return new RepositoryImpl(this, (RepositoryBean)fSession.get(RepositoryBeanImpl.class, name)); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#lookup(int, java.lang.String) - */ - public Lookup lookup(int version, String path) - { - String [] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - return rep.lookup(version, pathParts[1]); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#lookupDirectory(int, java.lang.String) - */ - public Lookup lookupDirectory(int version, String path) - { - String[] pathParts = SplitPath(path); - Repository rep = getRepositoryByName(pathParts[0]); - 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 AlfrescoRuntimeException("Invalid path: " + path); - } - return pathParts; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.SuperRepository#getStorageRoot() - */ - public String getStorageRoot() - { - return fStorage; - } -} diff --git a/source/java/org/alfresco/repo/avm/util/BulkLoad.java b/source/java/org/alfresco/repo/avm/util/BulkLoad.java new file mode 100644 index 0000000000..1a5a55e55c --- /dev/null +++ b/source/java/org/alfresco/repo/avm/util/BulkLoad.java @@ -0,0 +1,109 @@ +/* + * 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.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.alfresco.repo.avm.AVMService; +import org.alfresco.repo.avm.AVMServiceImpl; + +/** + * This takes a filesystem directory path and a repository path and name + * and bulk loads recursively from the filesystem. + * @author britt + */ +public class BulkLoad +{ + private AVMService fService; + + /** + * Bulk load from a filesystem directory. + * Syntax : BulkLoad storagepath (new|old) fspath reppath name + * @param args + */ + public static void main(String[] args) + { + if (args.length != 4) + { + System.err.println("Syntax: BulkLoad storagepath (new|old) fspath reppath"); + System.exit(1); + } + AVMServiceImpl service = new AVMServiceImpl(); + service.setStorage(args[0]); + service.init(args[1].equals("new")); + BulkLoad loader = new BulkLoad(service); + loader.recursiveLoad(args[2], args[3]); + service.createSnapshot("main"); + } + + /** + * Create a new one. + * @param service + */ + public BulkLoad(AVMService service) + { + fService = service; + } + + /** + * Recursively load content. + * @param fsPath The path in the filesystem. + * @param repPath + */ + public void recursiveLoad(String fsPath, String repPath) + { + System.out.println(fsPath); + File file = new File(fsPath); + String name = file.getName(); + if (file.isDirectory()) + { + fService.createDirectory(repPath, name); + String[] children = file.list(); + String baseName = repPath.endsWith("/") ? repPath + name : repPath + "/" + name; + for (String child : children) + { + recursiveLoad(fsPath + "/" + child, baseName); + } + } + else + { + try + { + InputStream in = new FileInputStream(file); + OutputStream out = fService.createFile(repPath, name); + byte[] buff = new byte[8192]; + int read = 0; + while ((read = in.read(buff)) != -1) + { + out.write(buff, 0, read); + } + out.close(); + in.close(); + } + catch (IOException e) + { + e.printStackTrace(System.err); + System.exit(1); + } + } + } +}