diff --git a/config/alfresco/avm-services-context.xml b/config/alfresco/avm-services-context.xml index e102dee36f..8920d704a4 100644 --- a/config/alfresco/avm-services-context.xml +++ b/config/alfresco/avm-services-context.xml @@ -277,6 +277,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 5a9a2080f2..87cdb01220 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -205,13 +205,20 @@ text/xml false - - + + jbpm alfresco/workflow/submit_processdefinition.xml text/xml false - + + + + jbpm + alfresco/workflow/changerequest_processdefinition.xml + text/xml + false + diff --git a/config/alfresco/model/wcmAppModel.xml b/config/alfresco/model/wcmAppModel.xml index 4c8c0c3d55..22351efe7a 100644 --- a/config/alfresco/model/wcmAppModel.xml +++ b/config/alfresco/model/wcmAppModel.xml @@ -386,6 +386,17 @@ + + + Content that has or can expire + + + Expiration Date + d:datetime + true + + + diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index ae6ae52cc8..37a5fe1509 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -206,4 +206,29 @@ 1 + + + + + + + org.alfresco.repo.avm.AVMExpiredContentJob + + + + + + + + + + + + + + + + 0 30 3 * * ? + + \ No newline at end of file diff --git a/config/alfresco/workflow/changerequest_processdefinition.xml b/config/alfresco/workflow/changerequest_processdefinition.xml new file mode 100644 index 0000000000..c33a311fed --- /dev/null +++ b/config/alfresco/workflow/changerequest_processdefinition.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + #{bpm_assignee} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow/wcm-workflow-messages.properties b/config/alfresco/workflow/wcm-workflow-messages.properties index 7d73fe8979..d5de053868 100644 --- a/config/alfresco/workflow/wcm-workflow-messages.properties +++ b/config/alfresco/workflow/wcm-workflow-messages.properties @@ -38,3 +38,15 @@ wcmwf_workflowmodel.property.wcmwf_reviewerCnt.title=Total Reviewed wcmwf_workflowmodel.property.wcmwf_reviewerCnt.description=Count of people who reviewed wcmwf_workflowmodel.property.wcmwf_approveCnt.title=Total Approved wcmwf_workflowmodel.property.wcmwf_approveCnt.description=Count of people who approved + +# Change Request Workflow + +wcmwf_changerequest.workflow.title=Change Request +wcmwf_changerequest.workflow.description=Assign an asset for modification + +# Change Request Task Definitions + +wcmwf_workflowmodel.type.wcmwf_submitChangeRequestTask.title=Submit Change Request +wcmwf_workflowmodel.type.wcmwf_submitChangeRequestTask.description=Submits a change request for one or more items +wcmwf_workflowmodel.type.wcmwf_changeRequestTask.title=Change Request +wcmwf_workflowmodel.type.wcmwf_changeRequestTask.description=Change Request diff --git a/config/alfresco/workflow/wcmWorkflowModel.xml b/config/alfresco/workflow/wcmWorkflowModel.xml index 0c97bd7b86..c3c7a232c8 100644 --- a/config/alfresco/workflow/wcmWorkflowModel.xml +++ b/config/alfresco/workflow/wcmWorkflowModel.xml @@ -115,6 +115,32 @@ bpm:assignee + + + + + + + + wcmwf:startTask + + bpm:assignee + + + + + bpm:workflowTask + + + + + + edit_wcm_package_item_actions + + + + bpm:assignee + diff --git a/source/java/org/alfresco/model/WCMAppModel.java b/source/java/org/alfresco/model/WCMAppModel.java index 4f0eaf8c41..248fdc0ca0 100644 --- a/source/java/org/alfresco/model/WCMAppModel.java +++ b/source/java/org/alfresco/model/WCMAppModel.java @@ -119,4 +119,8 @@ public interface WCMAppModel static final QName TYPE_RENDITION_PROPERTIES = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "renditionproperties"); static final QName PROP_MIMETYPE_FOR_RENDITION = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "mimetypeforrendition"); + + // Aspect to track content that expires + static final QName ASPECT_EXPIRES = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "expires"); + static final QName PROP_EXPIRATIONDATE = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "expirationDate"); } diff --git a/source/java/org/alfresco/repo/avm/AVMExpiredContentJob.java b/source/java/org/alfresco/repo/avm/AVMExpiredContentJob.java new file mode 100644 index 0000000000..c3fd7beacf --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMExpiredContentJob.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.avm; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Job to periodically execute the expired content processor. + * + *

+ * The following parameters are required: + *

+ * + * @author gavinc + */ +public class AVMExpiredContentJob implements Job +{ + /** + * Searches for expired content in web project's staging area and + * prompt the last modifier of the content to review it. + * + * @param context The job context + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + // get the expired content processor bean from the job context + AVMExpiredContentProcessor expiredContentProcessor = + (AVMExpiredContentProcessor)context.getJobDetail().getJobDataMap().get("expiredContentProcessor"); + if (expiredContentProcessor == null) + { + throw new JobExecutionException("Missing job data: expiredContentProcessor"); + } + + // execute the processor to do the actual work + expiredContentProcessor.execute(); + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java b/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java new file mode 100644 index 0000000000..6f14b801c6 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java @@ -0,0 +1,553 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.avm; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.config.JNDIConstants; +import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.sandbox.SandboxConstants; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.AVMStoreDescriptor; +import org.alfresco.service.cmr.avmsync.AVMSyncService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.DNSNameMangler; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Bean that is responsible for locating expired content and routing + * it for review to the most relevant user. + * + * @author gavinc + */ +public class AVMExpiredContentProcessor +{ + protected Map>> expiredContent; + protected AVMService avmService; + protected AVMSyncService avmSyncService; + protected NodeService nodeService; + protected WorkflowService workflowService; + protected PersonService personService; + protected PermissionService permissionService; + protected TransactionService transactionService; + + private static Log logger = LogFactory.getLog(AVMExpiredContentProcessor.class); + + private static final String WORKFLOW_NAME = "jbpm$wcmwf:changerequest"; + private static final String STORE_SEPARATOR = "--"; + private final static Pattern STORE_RELATIVE_PATH_PATTERN = Pattern.compile("[^:]+:(.+)"); + + public AVMExpiredContentProcessor() + { + } + + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + public void setAvmSyncService(AVMSyncService avmSyncService) + { + this.avmSyncService = avmSyncService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Executes the expired content processor. + * The work is performed within a transaction running as the system user. + */ + public void execute() + { + // setup a wrapper object to run the processor within a transaction. + AuthenticationUtil.RunAsWork authorisedWork = new AuthenticationUtil.RunAsWork() + { + public String doWork() throws Exception + { + TransactionWork expiredContentWork = new TransactionWork() + { + public String doWork() throws Exception + { + processExpiredContent(); + return null; + } + }; + + return TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, expiredContentWork); + } + }; + + // perform the work as the system user + AuthenticationUtil.runAs(authorisedWork, "admin"); + } + + /** + * Entry point. + */ + private void processExpiredContent() + { + // create the maps to hold the expired content for each user in each web project + this.expiredContent = new HashMap>>(8); + + // iterate through all AVM stores and focus only on staging main stores + List stores = avmService.getStores(); + if (logger.isDebugEnabled()) + logger.debug("Checking " + stores.size() + " AVM stores..."); + + for (AVMStoreDescriptor storeDesc : stores) + { + String storeName = storeDesc.getName(); + PropertyValue val = avmService.getStoreProperty(storeName, SandboxConstants.PROP_SANDBOX_STAGING_MAIN); + + if (val != null) + { + if (logger.isDebugEnabled()) + logger.debug("Searching store '" + storeName + "' for expired content..."); + + // crawl the whole directory tree looking for nodes with the + // content expiration aspect. + // TODO: This would be a LOT better and effecient using a search + // but it doesn't exist yet! + AVMNodeDescriptor rootNode = this.avmService.getStoreRoot(-1, storeName); + processFolder(storeName, rootNode); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Skipping store '" + storeName + "' as it is not a main staging store"); + } + } + + // show all the expired content if debug is on + if (logger.isDebugEnabled()) + logger.debug("Expired content to action:\n" + this.expiredContent); + + // iterate through each store that has expired content, then iterate through + // each user that has expired content in that store. For each user start + // a workflow assigned to them to review the expired content. + for (String storeName: this.expiredContent.keySet()) + { + Map> users = this.expiredContent.get(storeName); + for (String userName: users.keySet()) + { + List expiredContent = users.get(userName); + startWorkflow(userName, storeName, expiredContent); + } + } + } + + /** + * Recursively processes the given folder looking for expired content. + * + * @param storeName The name of the store the folder belongs to + * @param folder The folder to start the search in + */ + private void processFolder(String storeName, AVMNodeDescriptor folder) + { + // check supplied node is a folder + if (folder.isDirectory()) + { + // get listing of contents of supplied folder + Map nodes = this.avmService.getDirectoryListing(folder); + for (AVMNodeDescriptor node: nodes.values()) + { + if (node.isDirectory()) + { + // recurse through folders + processFolder(storeName, node); + } + else + { + // process the node + processNode(storeName, node); + } + } + } + } + + /** + * Processes the given node. + *

+ * If the 'wca:expires' aspect is applied and the wca:expired property + * is false the wca:expirationDate property is checked. If the date is + * today's date or prior to today the last modifier of the node is retrieved + * and the node's path added to the users list of expired content. + *

+ * + * @param storeName The name of the store the folder belongs to + * @param node The node to examine + */ + private void processNode(String storeName, AVMNodeDescriptor node) + { + // check supplied node is a file + if (node.isFile()) + { + // check for existence of expires aspect + String nodePath = node.getPath(); + if (this.avmService.hasAspect(-1, nodePath, WCMAppModel.ASPECT_EXPIRES)) + { + PropertyValue expirationDateProp = this.avmService.getNodeProperty(-1, nodePath, + WCMAppModel.PROP_EXPIRATIONDATE); + + if (logger.isDebugEnabled()) + logger.debug("Examining expiration date for '" + nodePath + "': " + + expirationDateProp.getStringValue()); + + if (expirationDateProp != null) + { + Date now = new Date(); + Date expirationDate = (Date)expirationDateProp.getValue(DataTypeDefinition.DATETIME); + + if (expirationDate != null && expirationDate.before(now)) + { + // get the map of expired content for the store + Map> storeExpiredContent = this.expiredContent.get(storeName); + if (storeExpiredContent == null) + { + storeExpiredContent = new HashMap>(4); + this.expiredContent.put(storeName, storeExpiredContent); + } + + // get the list of expired content for the last modifier of the node + String modifier = node.getLastModifier(); + List userExpiredContent = storeExpiredContent.get(modifier); + if (userExpiredContent == null) + { + userExpiredContent = new ArrayList(4); + storeExpiredContent.put(modifier, userExpiredContent); + } + + // add the content to the user's list for the current store + userExpiredContent.add(nodePath); + + if (logger.isDebugEnabled()) + logger.debug("Added " + nodePath + " to " + modifier + "'s list of expired content"); + + // change the expired flag on the expires aspect to true to indicate + // that it is being dealt with + this.avmService.setNodeProperty(nodePath, WCMAppModel.PROP_EXPIRATIONDATE, + new PropertyValue(DataTypeDefinition.DATETIME, null)); + + if (logger.isDebugEnabled()) + logger.debug("Reset expiration date for: " + nodePath); + } + } + } + } + } + + /** + * Starts a workflow for the given user prompting them to review the list of given + * expired content in the given store. + * + * @param userName The user the expired content should be sent to + * @param storeName The store the expired content is in + * @param expiredContent List of paths to expired content + */ + private void startWorkflow(String userName, String storeName, List expiredContent) + { + // find the 'Change Request' workflow + WorkflowDefinition wfDef = workflowService.getDefinitionByName(WORKFLOW_NAME); + WorkflowPath path = this.workflowService.startWorkflow(wfDef.id, null); + if (path != null) + { + // extract the start task + List tasks = this.workflowService.getTasksForWorkflowPath(path.id); + if (tasks.size() == 1) + { + WorkflowTask startTask = tasks.get(0); + + if (startTask.state == WorkflowTaskState.IN_PROGRESS) + { + // determine the user to assign the workflow to + String userStore = storeName + STORE_SEPARATOR + userName; + if (this.avmService.getStore(userStore) == null) + { + // use the creator of the store (the web project creator) to assign the + // workflow to + String storeCreator = this.avmService.getStore(storeName).getCreator(); + + if (logger.isDebugEnabled()) + logger.debug("'" + userName + "' is no longer assigned to web project. Using '" + + storeCreator + "' as they created store '" + storeName + "'"); + + userName = storeCreator; + } + + // lookup the NodeRef for the user + NodeRef assignee = this.personService.getPerson(userName); + + // create a workflow store layered over the users store + String workflowStoreName = createUserWorkflowSandbox(storeName, userStore); + + // create a workflow package with all the expired items + NodeRef workflowPackage = setupWorkflowPackage(workflowStoreName, expiredContent); + + // create the workflow parameters map + Map params = new HashMap(5); + params.put(WorkflowModel.ASSOC_PACKAGE, workflowPackage); + // TODO: Externalise the following string - ask Dave best place to add this + params.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "Expired Content"); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); + + // transition the workflow to send it to the users inbox + this.workflowService.updateTask(startTask.id, params, null, null); + this.workflowService.endTask(startTask.id, null); + + if (logger.isDebugEnabled()) + logger.debug("Started '" + WORKFLOW_NAME + "' workflow for user '" + + userName + "' in store '" + storeName + "'"); + } + } + } + } + + /** + * Creates a workflow sandbox for the given user store. This will create a + * workflow sandbox layered over the user's main store. + * + * @param stagingStore The name of the staging store the user sandbox is layered over + * @param userStore The name of the user store to create the workflow for + * @return The store name of the main store in the workflow sandbox + */ + private String createUserWorkflowSandbox(String stagingStore, String userStore) + { + // create the workflow 'main' store + String packageName = "workflow-" + GUID.generate(); + String workflowStoreName = userStore + STORE_SEPARATOR + packageName; + + this.avmService.createStore(workflowStoreName); + + if (logger.isDebugEnabled()) + logger.debug("Created user workflow sandbox store: " + workflowStoreName); + + // create a layered directory pointing to 'www' in the users store + this.avmService.createLayeredDirectory( + userStore + ":/" + JNDIConstants.DIR_DEFAULT_WWW, + workflowStoreName + ":/", JNDIConstants.DIR_DEFAULT_WWW); + + // tag the store with the store type + this.avmService.setStoreProperty(workflowStoreName, + SandboxConstants.PROP_SANDBOX_AUTHOR_WORKFLOW_MAIN, + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with the name of the author's store this one is layered over + this.avmService.setStoreProperty(workflowStoreName, + SandboxConstants.PROP_AUTHOR_NAME, + new PropertyValue(DataTypeDefinition.TEXT, userStore)); + + // tag the store, oddly enough, with its own store name for querying. + this.avmService.setStoreProperty(workflowStoreName, + QName.createQName(null, SandboxConstants.PROP_SANDBOX_STORE_PREFIX + workflowStoreName), + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with the DNS name property + String path = workflowStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + + "/" + JNDIConstants.DIR_DEFAULT_APPBASE; + // DNS name mangle the property name - can only contain value DNS characters! + String dnsProp = SandboxConstants.PROP_DNS + DNSNameMangler.MakeDNSName(userStore, packageName); + this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, dnsProp), + new PropertyValue(DataTypeDefinition.TEXT, path)); + + // the main workflow store depends on the main user store (dist=1) + String prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + userStore; + this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 1)); + + // The main workflow store depends on the main staging store (dist=2) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + stagingStore; + this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 2)); + + // snapshot the store + this.avmService.createSnapshot(workflowStoreName, null, null); + + // create the workflow 'preview' store + String previewStoreName = workflowStoreName + STORE_SEPARATOR + "preview"; + this.avmService.createStore(previewStoreName); + + if (logger.isDebugEnabled()) + logger.debug("Created user workflow sandbox preview store: " + previewStoreName); + + // create a layered directory pointing to 'www' in the workflow 'main' store + this.avmService.createLayeredDirectory( + workflowStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW, + previewStoreName + ":/", JNDIConstants.DIR_DEFAULT_WWW); + + // tag the store with the store type + this.avmService.setStoreProperty(previewStoreName, SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW, + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with its own store name for querying. + avmService.setStoreProperty(previewStoreName, + QName.createQName(null, SandboxConstants.PROP_SANDBOX_STORE_PREFIX + previewStoreName), + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with the DNS name property + path = previewStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + + "/" + JNDIConstants.DIR_DEFAULT_APPBASE; + // DNS name mangle the property name - can only contain value DNS characters! + dnsProp = SandboxConstants.PROP_DNS + DNSNameMangler.MakeDNSName(userStore, packageName, "preview"); + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, dnsProp), + new PropertyValue(DataTypeDefinition.TEXT, path)); + + // The preview worfkflow store depends on the main workflow store (dist=1) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + workflowStoreName; + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 1)); + + // The preview workflow store depends on the main user store (dist=2) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + userStore; + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 2)); + + // The preview workflow store depends on the main staging store (dist=3) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + stagingStore; + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 3)); + + // snapshot the store + this.avmService.createSnapshot(previewStoreName, null, null); + + // tag all related stores to indicate that they are part of a single sandbox + QName sandboxIdProp = QName.createQName(SandboxConstants.PROP_SANDBOXID + GUID.generate()); + this.avmService.setStoreProperty(workflowStoreName, sandboxIdProp, + new PropertyValue(DataTypeDefinition.TEXT, null)); + this.avmService.setStoreProperty(previewStoreName, sandboxIdProp, + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // return the main workflow store name + return workflowStoreName; + } + + /** + * Sets up a workflow package from the given main workflow store and applies + * the list of paths as modified items within the main workflow store. + * + * @param workflowStoreName The main workflow store to setup + * @param expiredContent The expired content + * @return The NodeRef representing the workflow package + */ + private NodeRef setupWorkflowPackage(String workflowStoreName, List expiredContent) + { + // create package paths (layered to user sandbox area as target) + String packagesPath = workflowStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW; + +// List diffs = new ArrayList(expiredContent.size()); + for (final String srcPath : expiredContent) + { + final Matcher m = STORE_RELATIVE_PATH_PATTERN.matcher(srcPath); + String relPath = m.matches() && m.group(1).length() != 0 ? m.group(1) : null; + String pathInWorkflowStore = workflowStoreName + ":" + relPath; + + // TODO: check whether the path is already modified in the users + // sandbox, if it is just create a new AVMDifference object + // otherwise we need to force a copy on write operation +// diffs.add(new AVMDifference(-1, srcPath, +// -1, pathInWorkflowStore, +// AVMDifference.NEWER)); +// for (AVMDifference d : this.avmSyncService.compare(-1, packageAvmPath, +// -1, stagingAvmPath, +// null)) +// { +// if (LOGGER.isDebugEnabled()) +// LOGGER.debug("got difference " + d); +// if (d.getDifferenceCode() == AVMDifference.NEWER || +// d.getDifferenceCode() == AVMDifference.CONFLICT) +// { +// this.addAVMNode(new AVMNode(this.avmService.lookup(d.getSourceVersion(), +// d.getSourcePath(), +// true))); +// } +// } + + this.avmService.forceCopy(pathInWorkflowStore); + } + + // write changes to layer so files are marked as modified +// avmSyncService.update(diffs, null, true, true, false, false, null, null); + + // convert package to workflow package + AVMNodeDescriptor packageDesc = avmService.lookup(-1, packagesPath); + NodeRef packageNodeRef = workflowService.createPackage( + AVMNodeConverter.ToNodeRef(-1, packageDesc.getPath())); + this.nodeService.setProperty(packageNodeRef, WorkflowModel.PROP_IS_SYSTEM_PACKAGE, true); + + // apply global permission to workflow package + this.permissionService.setPermission(packageNodeRef, PermissionService.ALL_AUTHORITIES, + PermissionService.ALL_PERMISSIONS, true); + + return packageNodeRef; + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMExpiredContentTest.java b/source/java/org/alfresco/repo/avm/AVMExpiredContentTest.java new file mode 100644 index 0000000000..a32e57ba33 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMExpiredContentTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.avm; + +import junit.framework.TestCase; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avmsync.AVMSyncService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @see org.alfresco.repo.avm.AVMEXpiredContentProcessor + * + * @author gavinc + */ +public class AVMExpiredContentTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private AVMExpiredContentProcessor processor; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); + AVMService avmService = serviceRegistry.getAVMService(); + AVMSyncService avmSyncService = serviceRegistry.getAVMSyncService(); + NodeService nodeService = serviceRegistry.getNodeService(); + PermissionService permissionService = serviceRegistry.getPermissionService(); + PersonService personService = serviceRegistry.getPersonService(); + TransactionService transactionService = serviceRegistry.getTransactionService(); + WorkflowService workflowService = serviceRegistry.getWorkflowService(); + + // construct the test processor + this.processor = new AVMExpiredContentProcessor(); + this.processor.setAvmService(avmService); + this.processor.setAvmSyncService(avmSyncService); + this.processor.setNodeService(nodeService); + this.processor.setPermissionService(permissionService); + this.processor.setPersonService(personService); + this.processor.setTransactionService(transactionService); + this.processor.setWorkflowService(workflowService); + } + + public void testProcessor() throws Exception + { + this.processor.execute(); + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMStoreImpl.java b/source/java/org/alfresco/repo/avm/AVMStoreImpl.java index bf9367a50f..7d6ec8f1ef 100644 --- a/source/java/org/alfresco/repo/avm/AVMStoreImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMStoreImpl.java @@ -1,1475 +1,1475 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" */ - -package org.alfresco.repo.avm; - -import java.io.File; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.alfresco.model.ContentModel; -import org.alfresco.model.WCMModel; -import org.alfresco.repo.avm.AVMAspectName; -import org.alfresco.repo.avm.util.RawServices; -import org.alfresco.repo.avm.util.SimplePath; -import org.alfresco.repo.domain.DbAccessControlList; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.service.cmr.avm.AVMBadArgumentException; -import org.alfresco.service.cmr.avm.AVMException; -import org.alfresco.service.cmr.avm.AVMExistsException; -import org.alfresco.service.cmr.avm.AVMNodeDescriptor; -import org.alfresco.service.cmr.avm.AVMNotFoundException; -import org.alfresco.service.cmr.avm.AVMStoreDescriptor; -import org.alfresco.service.cmr.avm.AVMWrongTypeException; -import org.alfresco.service.cmr.avm.VersionDescriptor; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; - -/** - * A Repository contains a current root directory and a list of - * root versions. Each root version corresponds to a separate snapshot - * operation. - * @author britt - */ -public class AVMStoreImpl implements AVMStore, Serializable -{ - static final long serialVersionUID = -1485972568675732904L; - - /** - * The primary key. - */ - private long fID; - - /** - * The name of this AVMStore. - */ - 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 AVMRepository. - */ - transient private AVMRepository fAVMRepository; - - /** - * Default constructor. - */ - protected AVMStoreImpl() - { - fAVMRepository = AVMRepository.GetInstance(); - } - - /** - * Make a brand new AVMStore. - * @param repo The AVMRepository. - * @param name The name of the AVMStore. - */ - public AVMStoreImpl(AVMRepository repo, String name) - { - // Make ourselves up and save. - fAVMRepository = repo; - fName = name; - fNextVersionID = 0; - fRoot = null; - AVMDAOs.Instance().fAVMStoreDAO.save(this); - String creator = RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); - if (creator == null) - { - creator = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); - } - setProperty(ContentModel.PROP_CREATOR, new PropertyValue(null, creator)); - setProperty(ContentModel.PROP_CREATED, new PropertyValue(null, new Date(System.currentTimeMillis()))); - // Make up the initial version record and save. - long time = System.currentTimeMillis(); - fRoot = new PlainDirectoryNodeImpl(this); - fRoot.setIsRoot(true); - AVMDAOs.Instance().fAVMNodeDAO.save(fRoot); - VersionRoot versionRoot = new VersionRootImpl(this, - fRoot, - fNextVersionID, - time, - creator, - "Initial Empty Version.", - "Initial Empty Version."); - fNextVersionID++; - AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); - } - - /** - * Setter for hibernate. - * @param id The primary key. - */ - protected void setId(long id) - { - fID = id; - } - - /** - * Get the primary key. - * @return The primary key. - */ - public long getId() - { - return fID; - } - - /** - * Set a new root for this. - * @param root - */ - public void setNewRoot(DirectoryNode root) - { - fRoot = root; - fRoot.setIsRoot(true); - } - - /** - * Snapshot this store. This creates a new version record. - * @return The version id of the new snapshot. - */ - @SuppressWarnings("unchecked") - public int createSnapshot(String tag, String description, Map snapShotMap) - { - VersionRoot lastVersion = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(this); - List layeredEntries = - AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.get(lastVersion); - // Is there no need for a snapshot? - if (!fRoot.getIsNew() && layeredEntries.size() == 0) - { - // So, we set the tag and description fields of the latest version. - if (tag != null || description != null) - { - lastVersion.setTag(tag); - lastVersion.setDescription(description); - } - snapShotMap.put(fName, lastVersion.getVersionID()); - return lastVersion.getVersionID(); - } - snapShotMap.put(fName, fNextVersionID); - // Force copies on all the layered nodes from last snapshot. - for (VersionLayeredNodeEntry entry : layeredEntries) - { - String path = entry.getPath(); - path = path.substring(path.indexOf(':') + 1); - Lookup lookup = lookup(-1, path, false, false); - if (lookup == null) - { - continue; - } - if (lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY && - lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_FILE) - { - continue; - } - if (lookup.getCurrentNode().getIsNew()) - { - continue; - } - String parentName[] = AVMNodeConverter.SplitBase(entry.getPath()); - parentName[0] = parentName[0].substring(parentName[0].indexOf(':') + 1); - lookup = lookupDirectory(-1, parentName[0], true); - DirectoryNode parent = (DirectoryNode)lookup.getCurrentNode(); - AVMNode child = parent.lookupChild(lookup, parentName[1], false); - // TODO This is funky. Need to look carefully to see that this call - // does exactly what's needed. - lookup.add(child, parentName[1], false); - AVMNode newChild = null; - if (child.getType() == AVMNodeType.LAYERED_DIRECTORY) - { - newChild = child.copy(lookup); - } - else - { - newChild = ((LayeredFileNode)child).copyLiterally(lookup); - } - parent.putChild(parentName[1], newChild); - } - // Clear out the new nodes. - List newInRep = AVMDAOs.Instance().fAVMNodeDAO.getNewInStore(this); - List layeredNodes = new ArrayList(); - for (AVMNode newGuy : newInRep) - { - newGuy.setStoreNew(null); - Layered layered = null; - if (newGuy.getType() == AVMNodeType.LAYERED_DIRECTORY && - ((LayeredDirectoryNode)newGuy).getPrimaryIndirection()) - { - layered = (Layered)AVMNodeUnwrapper.Unwrap(newGuy); - } - if (newGuy.getType() == AVMNodeType.LAYERED_FILE) - { - layered = (Layered)AVMNodeUnwrapper.Unwrap(newGuy); - } - if (layered != null) - { - layeredNodes.add(newGuy); - String indirection = layered.getIndirection(); - String storeName = indirection.substring(0, indirection.indexOf(':')); - if (!snapShotMap.containsKey(storeName)) - { - AVMStore store = AVMDAOs.Instance().fAVMStoreDAO.getByName(storeName); - if (store == null) - { - layered.setIndirectionVersion(-1); - } - else - { - store.createSnapshot(null, null, snapShotMap); - layered.setIndirectionVersion(snapShotMap.get(storeName)); - } - } - else - { - layered.setIndirectionVersion(snapShotMap.get(storeName)); - } - } - } - // Make up a new version record. - String user = RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); - if (user == null) - { - user = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); - } - VersionRoot versionRoot = new VersionRootImpl(this, - fRoot, - fNextVersionID, - System.currentTimeMillis(), - user, - tag, - description); - AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); - for (AVMNode node : layeredNodes) - { - List paths = fAVMRepository.getVersionPaths(versionRoot, node); - for (String path : paths) - { - VersionLayeredNodeEntry entry = - new VersionLayeredNodeEntryImpl(versionRoot, path); - AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.save(entry); - } - } - // Increment the version id. - fNextVersionID++; - return fNextVersionID - 1; - } - - /** - * 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, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - AVMNode child = dir.lookupChild(lPath, name, true); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - 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, null); - ((LayeredDirectoryNodeImpl)newDir).setPrimaryIndirection(false); - ((LayeredDirectoryNodeImpl)newDir).setLayerID(lPath.getTopLayer().getLayerID()); - } - else - { - newDir = new PlainDirectoryNodeImpl(this); - } - // newDir.setVersionID(getNextVersionID()); - if (child != null) - { - newDir.setAncestor(child); - } - dir.updateModTime(); - dir.putChild(name, newDir); - } - - /** - * 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, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + dstPath + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - AVMNode child = dir.lookupChild(lPath, name, true); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - LayeredDirectoryNode newDir = - new LayeredDirectoryNodeImpl(srcPath, this, null); - 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(fAVMRepository.issueLayerID()); - } - if (child != null) - { - newDir.setAncestor(child); - } - dir.updateModTime(); - dir.putChild(name, newDir); - // 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. - * initial content. - */ - public OutputStream createFile(String path, String name) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - AVMNode child = dir.lookupChild(lPath, name, true); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - PlainFileNodeImpl file = new PlainFileNodeImpl(this); - // file.setVersionID(getNextVersionID()); - dir.updateModTime(); - dir.putChild(name, file); - if (child != null) - { - file.setAncestor(child); - } - file.setContentData(new ContentData(null, - RawServices.Instance().getMimetypeService().guessMimetype(name), - -1, - "UTF-8")); - ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); - return writer.getContentOutputStream(); - } - - /** - * Create a file with the given contents. - * @param path The path to the containing directory. - * @param name The name to give the new file. - * @param data The contents. - */ - public void createFile(String path, String name, File data) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - AVMNode child = dir.lookupChild(lPath, name, true); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - PlainFileNodeImpl file = new PlainFileNodeImpl(this); - // file.setVersionID(getNextVersionID()); - dir.updateModTime(); - dir.putChild(name, file); - if (child != null) - { - file.setAncestor(child); - } - file.setContentData(new ContentData(null, - RawServices.Instance().getMimetypeService().guessMimetype(name), - -1, - "UTF-8")); - ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); - writer.putContent(data); - } - - /** - * 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, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + dstPath + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - AVMNode child = dir.lookupChild(lPath, name, true); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - // TODO Reexamine decision to not check validity of srcPath. - LayeredFileNodeImpl newFile = - new LayeredFileNodeImpl(srcPath, this); - if (child != null) - { - newFile.setAncestor(child); - } - dir.updateModTime(); - dir.putChild(name, newFile); - // 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) - { - ContentReader reader = getContentReader(version, path); - if (reader == null) - { - // TODO This is wrong, wrong, wrong. Do something about it - // sooner rather than later. - throw new AVMNotFoundException(path + " has no content."); - } - return reader.getContentInputStream(); - } - - /** - * Get a ContentReader from a file. - * @param version The version to look under. - * @param path The path to the file. - * @return A ContentReader. - */ - public ContentReader getContentReader(int version, String path) - { - NodeRef nodeRef = AVMNodeConverter.ToNodeRef(version, fName + ":" + path); - return RawServices.Instance().getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT); - } - - /** - * Get a ContentWriter to a file. - * @param path The path to the file. - * @return A ContentWriter. - */ - public ContentWriter createContentWriter(String path) - { - NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, fName + ":" + path); - ContentWriter writer = - RawServices.Instance().getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - return writer; - } - - /** - * 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 SortedMap getListing(int version, String path, - boolean includeDeleted) - { - Lookup lPath = lookupDirectory(version, path, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - Map listing = dir.getListing(lPath, includeDeleted); - return translateListing(listing, lPath); - } - - /** - * Get the list of nodes directly contained in a directory. - * @param version The version to look under. - * @param path The path to the directory. - * @return A Map of names to descriptors. - */ - public SortedMap getListingDirect(int version, String path, - boolean includeDeleted) - { - Lookup lPath = lookupDirectory(version, path, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (lPath.isLayered() && dir.getType() != AVMNodeType.LAYERED_DIRECTORY) - { - return new TreeMap(); - } - Map listing = dir.getListingDirect(lPath, includeDeleted); - return translateListing(listing, lPath); - } - - /** - * Helper to convert an internal representation of a directory listing - * to an external representation. - * @param listing The internal listing, a Map of names to nodes. - * @param lPath The Lookup for the directory. - * @return A Map of names to descriptors. - */ - private SortedMap - translateListing(Map listing, Lookup lPath) - { - SortedMap results = new TreeMap(); - for (String name : listing.keySet()) - { - // TODO consider doing this at a lower level. - AVMNode child = AVMNodeUnwrapper.Unwrap(listing.get(name)); - AVMNodeDescriptor desc = child.getDescriptor(lPath, name); - results.put(name, desc); - } - return results; - } - - /** - * Get the names of the deleted nodes in a directory. - * @param version The version to look under. - * @param path The path to the directory. - * @return A List of names. - */ - public List getDeleted(int version, String path) - { - Lookup lPath = lookupDirectory(version, path, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - return dir.getDeletedNames(); - } - - /** - * Get an output stream to a file. - * @param path The path to the file. - * @return An OutputStream. - */ - public OutputStream getOutputStream(String path) - { - ContentWriter writer = createContentWriter(path); - return writer.getContentOutputStream(); - } - - /** - * 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) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (dir.lookupChild(lPath, name, false) == null) - { - throw new AVMNotFoundException("Does not exist: " + name); - } - dir.removeChild(lPath, name); - dir.updateModTime(); - } - - /** - * 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, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + dirPath + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (node.getType() != AVMNodeType.LAYERED_DIRECTORY) - { - throw new AVMWrongTypeException("Not a layered directory: " + dirPath); - } - ((LayeredDirectoryNode)node).uncover(lPath, name); - node.updateModTime(); - } - - // 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 versions for this AVMStore. - * @return A Set of version ids. - */ - @SuppressWarnings("unchecked") - public List getVersions() - { - List versions = AVMDAOs.Instance().fVersionRootDAO.getAllInAVMStore(this); - List descs = new ArrayList(); - for (VersionRoot vr : versions) - { - VersionDescriptor desc = - new VersionDescriptor(fName, - vr.getVersionID(), - vr.getCreator(), - vr.getCreateDate(), - vr.getTag(), - vr.getDescription()); - descs.add(desc); - } - return descs; - } - - /** - * Get the versions between the given dates (inclusive). From or - * to may be null but not both. - * @param from The earliest date. - * @param to The latest date. - * @return The Set of matching version IDs. - */ - @SuppressWarnings("unchecked") - public List getVersions(Date from, Date to) - { - List versions = AVMDAOs.Instance().fVersionRootDAO.getByDates(this, from, to); - List descs = new ArrayList(); - for (VersionRoot vr : versions) - { - VersionDescriptor desc = - new VersionDescriptor(fName, - vr.getVersionID(), - vr.getCreator(), - vr.getCreateDate(), - vr.getTag(), - vr.getDescription()); - descs.add(desc); - } - return descs; - } - - /** - * Get the AVMRepository. - * @return The AVMRepository - */ - public AVMRepository getAVMRepository() - { - return fAVMRepository; - } - - /** - * Lookup up a path. - * @param version The version to look in. - * @param path The path to look up. - * @param write Whether this is in the context of a write. - * @return A Lookup object. - */ - public Lookup lookup(int version, String path, boolean write, boolean includeDeleted) - { - SimplePath sPath = new SimplePath(path); - return RawServices.Instance().getLookupCache().lookup(this, version, sPath, write, includeDeleted); - } - - /** - * 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 - { - root = AVMDAOs.Instance().fAVMNodeDAO.getAVMStoreRoot(this, version); - } - return root.getDescriptor("main:", "", null, -1); - } - - /** - * Lookup a node and insist that it is a directory. - * @param version The version to look under. - * @param path The path to the directory. - * @param write Whether this is in a write context. - * @return A Lookup object. - */ - public Lookup lookupDirectory(int version, String path, boolean write) - { - // Just do a regular lookup and assert that the last element - // is a directory. - Lookup lPath = lookup(version, path, write, false); - if (lPath == null) - { - return null; - } - if (lPath.getCurrentNode().getType() != AVMNodeType.PLAIN_DIRECTORY && - lPath.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY) - { - return null; - } - 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, false, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - if (!lPath.isLayered()) - { - return null; - } - AVMNode node = lPath.getCurrentNode(); - if (node.getType() == AVMNodeType.LAYERED_DIRECTORY) - { - LayeredDirectoryNode dir = (LayeredDirectoryNode)node; - return dir.getUnderlying(lPath); - } - else if (node.getType() == AVMNodeType.LAYERED_FILE) - { - LayeredFileNode file = (LayeredFileNode)node; - return file.getUnderlying(lPath); - } - return lPath.getIndirectionPath(); - } - - /** - * Make the indicated node a primary indirection. - * @param path The path to the node. - */ - public void makePrimary(String path) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!lPath.isLayered()) - { - throw new AVMException("Not in a layered context: " + path); - } - dir.turnPrimary(lPath); - dir.updateModTime(); - } - - /** - * 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, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!lPath.isLayered()) - { - throw new AVMException("Not in a layered context: " + path); - } - dir.retarget(lPath, target); - dir.updateModTime(); - } - - /** - * Set the name of this AVMStore. - * @param name - */ - public void setName(String name) - { - fName = name; - } - - /** - * Get the name of this AVMStore. - * @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; - } - - /** - * This gets the last extant version id. - */ - public int getLastVersionID() - { - return AVMDAOs.Instance().fVersionRootDAO.getMaxVersionID(this); - } - - /** - * 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; - } - - /** - * Equals override. - * @param obj - * @return Equality. - */ - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (!(obj instanceof AVMStore)) - { - return false; - } - return fID == ((AVMStore)obj).getId(); - } - - /** - * Get a hash code. - * @return The hash code. - */ - @Override - public int hashCode() - { - return (int)fID; - } - - /** - * 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"); - } - VersionRoot vRoot = AVMDAOs.Instance().fVersionRootDAO.getByVersionID(this, version); - if (vRoot == null) - { - throw new AVMNotFoundException("Version not found."); - } - AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.delete(vRoot); - AVMNode root = vRoot.getRoot(); - root.setIsRoot(false); - AVMDAOs.Instance().fAVMNodeDAO.update(root); - AVMDAOs.Instance().fVersionRootDAO.delete(vRoot); - if (root.equals(fRoot)) - { - // We have to set a new current root. - // TODO More hibernate goofiness to compensate for: fSuper.getSession().flush(); - vRoot = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(this); - fRoot = vRoot.getRoot(); - AVMDAOs.Instance().fAVMStoreDAO.update(this); - } - } - - /** - * Get the descriptor for this. - * @return An AVMStoreDescriptor - */ - public AVMStoreDescriptor getDescriptor() - { - return new AVMStoreDescriptor(fName, - getProperty(ContentModel.PROP_CREATOR).getStringValue(), - ((Date)getProperty(ContentModel.PROP_CREATED).getValue(DataTypeDefinition.DATE)).getTime()); - } - - /** - * Set the opacity of a layered directory. An opaque directory hides - * what is pointed at by its indirection. - * @param path The path to the layered directory. - * @param opacity True is opaque; false is not. - */ - public void setOpacity(String path, boolean opacity) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof LayeredDirectoryNode)) - { - throw new AVMWrongTypeException("Not a LayeredDirectoryNode."); - } - ((LayeredDirectoryNode)node).setOpacity(opacity); - node.updateModTime(); - } - - // TODO Does it make sense to set properties on DeletedNodes? - /** - * Set a property on a node. - * @param path The path to the node. - * @param name The name of the property. - * @param value The value to set. - */ - public void setNodeProperty(String path, QName name, PropertyValue value) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - node.setProperty(name, value); - node.setGuid(GUID.generate()); - } - - /** - * Set a collection of properties on a node. - * @param path The path to the node. - * @param properties The Map of QNames to PropertyValues. - */ - public void setNodeProperties(String path, Map properties) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - node.setProperties(properties); - node.setGuid(GUID.generate()); - } - - /** - * Get a property by name. - * @param version The version to lookup. - * @param path The path to the node. - * @param name The name of the property. - * @return A PropertyValue or null if not found. - */ - public PropertyValue getNodeProperty(int version, String path, QName name) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - return node.getProperty(name); - } - - /** - * Get all the properties associated with a node. - * @param version The version to lookup. - * @param path The path to the node. - * @return A Map of QNames to PropertyValues. - */ - public Map getNodeProperties(int version, String path) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - return node.getProperties(); - } - - /** - * Delete a single property from a node. - * @param path The path to the node. - * @param name The name of the property. - */ - public void deleteNodeProperty(String path, QName name) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - node.setGuid(GUID.generate()); - node.deleteProperty(name); - } - - /** - * Delete all properties from a node. - * @param path The path to the node. - */ - public void deleteNodeProperties(String path) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - node.setGuid(GUID.generate()); - node.deleteProperties(); - } - - /** - * Set a property on this store. Replaces if property already exists. - * @param name The QName of the property. - * @param value The actual PropertyValue. - */ - public void setProperty(QName name, PropertyValue value) - { - AVMStoreProperty prop = new AVMStorePropertyImpl(); - prop.setStore(this); - prop.setName(name); - prop.setValue(value); - AVMDAOs.Instance().fAVMStorePropertyDAO.save(prop); - } - - /** - * Set a group of properties on this store. Replaces any property that exists. - * @param properties A Map of QNames to PropertyValues to set. - */ - public void setProperties(Map properties) - { - for (QName name : properties.keySet()) - { - setProperty(name, properties.get(name)); - } - } - - /** - * Get a property by name. - * @param name The QName of the property to fetch. - * @return The PropertyValue or null if non-existent. - */ - public PropertyValue getProperty(QName name) - { - AVMStoreProperty prop = AVMDAOs.Instance().fAVMStorePropertyDAO.get(this, name); - if (prop == null) - { - return null; - } - return prop.getValue(); - } - - /** - * Get all the properties associated with this node. - * @return A Map of the properties. - */ - public Map getProperties() - { - List props = - AVMDAOs.Instance().fAVMStorePropertyDAO.get(this); - Map retVal = new HashMap(); - for (AVMStoreProperty prop : props) - { - retVal.put(prop.getName(), prop.getValue()); - } - return retVal; - } - - /** - * Delete a property. - * @param name The name of the property to delete. - */ - public void deleteProperty(QName name) - { - AVMDAOs.Instance().fAVMStorePropertyDAO.delete(this, name); - } - - /** - * Get the ContentData on a file. - * @param version The version to look under. - * @param path The path to the file. - * @return The ContentData corresponding to the file. - */ - public ContentData getContentDataForRead(int version, String path) - { - Lookup lPath = lookup(version, path, false, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AVMWrongTypeException("File Expected."); - } - return ((FileNode)node).getContentData(lPath); - } - - /** - * Get the ContentData on a file for writing. - * @param path The path to the file. - * @return The ContentData corresponding to the file. - */ - public ContentData getContentDataForWrite(String path) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AVMWrongTypeException("File Expected."); - } - node.updateModTime(); - node.setGuid(GUID.generate()); - return ((FileNode)node).getContentData(lPath); - } - - /** - * Set the ContentData for a file. - * @param path The path to the file. - * @param data The ContentData to set. - */ - public void setContentData(String path, ContentData data) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AVMWrongTypeException("File Expected."); - } - ((FileNode)node).setContentData(data); - } - - /** - * Set meta data, aspects, properties, acls, from another node. - * @param path The path to the node to set metadata on. - * @param from The node to get the metadata from. - */ - public void setMetaDataFrom(String path, AVMNode from) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path not found: " + path); - } - AVMNode node = lPath.getCurrentNode(); - node.copyMetaDataFrom(from); - node.setGuid(GUID.generate()); - } - - /** - * Add an aspect to a node. - * @param path The path to the node. - * @param aspectName The name of the aspect. - */ - public void addAspect(String path, QName aspectName) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (AVMDAOs.Instance().fAVMAspectNameDAO.exists(node, aspectName)) - { - throw new AVMExistsException("Aspect exists."); - } - AVMAspectName newName = - new AVMAspectNameImpl(); - newName.setNode(node); - newName.setName(aspectName); - node.setGuid(GUID.generate()); - AVMDAOs.Instance().fAVMAspectNameDAO.save(newName); - } - - /** - * Get all aspects on a given node. - * @param version The version to look under. - * @param path The path to the node. - * @return A List of the QNames of the aspects. - */ - public List getAspects(int version, String path) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - List names = - AVMDAOs.Instance().fAVMAspectNameDAO.get(node); - ArrayList result = new ArrayList(); - for (AVMAspectName name : names) - { - result.add(name.getName()); - } - return result; - } - - /** - * Remove an aspect and all its properties from a node. - * @param path The path to the node. - * @param aspectName The name of the aspect. - */ - public void removeAspect(String path, QName aspectName) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - AVMDAOs.Instance().fAVMAspectNameDAO.delete(node, aspectName); - AspectDefinition def = RawServices.Instance().getDictionaryService().getAspect(aspectName); - Map properties = - def.getProperties(); - for (QName name : properties.keySet()) - { - AVMDAOs.Instance().fAVMNodePropertyDAO.delete(node, name); - } - node.setGuid(GUID.generate()); - } - - /** - * Does a given node have a given aspect. - * @param version The version to look under. - * @param path The path to the node. - * @param aspectName The name of the aspect. - * @return Whether the node has the aspect. - */ - public boolean hasAspect(int version, String path, QName aspectName) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - return AVMDAOs.Instance().fAVMAspectNameDAO.exists(node, aspectName); - } - - /** - * Set the ACL on a node. - * @param path The path to the node. - * @param acl The ACL to set. - */ - public void setACL(String path, DbAccessControlList acl) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - node.setAcl(acl); - node.setGuid(GUID.generate()); - } - - /** - * Get the ACL on a node. - * @param version The version to look under. - * @param path The path to the node. - * @return The ACL. - */ - public DbAccessControlList getACL(int version, String path) - { - Lookup lPath = lookup(version, path, false, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - return lPath.getCurrentNode().getAcl(); - } - - /** - * Link a node intro a directory, directly. - * @param parentPath The path to the directory. - * @param name The name to give the parent. - * @param toLink The node to link. - */ - public void link(String parentPath, String name, AVMNodeDescriptor toLink) - { - Lookup lPath = lookupDirectory(-1, parentPath, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + parentPath + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - dir.link(lPath, name, toLink); - } - - /** - * Revert a head path to a given version. This works by cloning - * the version to revert to, and then linking that new version into head. - * The reverted version will have the previous head version as ancestor. - * @param path The path to the parent directory. - * @param name The name of the node to revert. - * @param toRevertTo The descriptor of the version to revert to. - */ - public void revert(String path, String name, AVMNodeDescriptor toRevertTo) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - AVMNode child = dir.lookupChild(lPath, name, true); - if (child == null) - { - throw new AVMNotFoundException("Node not found: " + name); - } - AVMNode revertNode = AVMDAOs.Instance().fAVMNodeDAO.getByID(toRevertTo.getId()); - if (revertNode == null) - { - throw new AVMNotFoundException(toRevertTo.toString()); - } - AVMNode toLink = revertNode.copy(lPath); - dir.putChild(name, toLink); - toLink.changeAncestor(child); - toLink.setVersionID(child.getVersionID() + 1); - if (AVMDAOs.Instance().fAVMAspectNameDAO.exists(toLink, WCMModel.ASPECT_REVERTED)) - { - AVMDAOs.Instance().fAVMAspectNameDAO.delete(toLink, WCMModel.ASPECT_REVERTED); - } - AVMAspectName aspect = new AVMAspectNameImpl(); - aspect.setNode(toLink); - aspect.setName(WCMModel.ASPECT_REVERTED); - AVMDAOs.Instance().fAVMAspectNameDAO.save(aspect); - PropertyValue value = new PropertyValue(null, toRevertTo.getId()); - toLink.setProperty(WCMModel.PROP_REVERTED_ID, value); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMStore#setGuid(java.lang.String, java.lang.String) - */ - public void setGuid(String path, String guid) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path not found: " + path); - } - AVMNode node = lPath.getCurrentNode(); - node.setGuid(guid); - } -} +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" */ + +package org.alfresco.repo.avm; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.WCMModel; +import org.alfresco.repo.avm.AVMAspectName; +import org.alfresco.repo.avm.util.RawServices; +import org.alfresco.repo.avm.util.SimplePath; +import org.alfresco.repo.domain.DbAccessControlList; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.service.cmr.avm.AVMBadArgumentException; +import org.alfresco.service.cmr.avm.AVMException; +import org.alfresco.service.cmr.avm.AVMExistsException; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMNotFoundException; +import org.alfresco.service.cmr.avm.AVMStoreDescriptor; +import org.alfresco.service.cmr.avm.AVMWrongTypeException; +import org.alfresco.service.cmr.avm.VersionDescriptor; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; + +/** + * A Repository contains a current root directory and a list of + * root versions. Each root version corresponds to a separate snapshot + * operation. + * @author britt + */ +public class AVMStoreImpl implements AVMStore, Serializable +{ + static final long serialVersionUID = -1485972568675732904L; + + /** + * The primary key. + */ + private long fID; + + /** + * The name of this AVMStore. + */ + 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 AVMRepository. + */ + transient private AVMRepository fAVMRepository; + + /** + * Default constructor. + */ + protected AVMStoreImpl() + { + fAVMRepository = AVMRepository.GetInstance(); + } + + /** + * Make a brand new AVMStore. + * @param repo The AVMRepository. + * @param name The name of the AVMStore. + */ + public AVMStoreImpl(AVMRepository repo, String name) + { + // Make ourselves up and save. + fAVMRepository = repo; + fName = name; + fNextVersionID = 0; + fRoot = null; + AVMDAOs.Instance().fAVMStoreDAO.save(this); + String creator = RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); + if (creator == null) + { + creator = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); + } + setProperty(ContentModel.PROP_CREATOR, new PropertyValue(null, creator)); + setProperty(ContentModel.PROP_CREATED, new PropertyValue(null, new Date(System.currentTimeMillis()))); + // Make up the initial version record and save. + long time = System.currentTimeMillis(); + fRoot = new PlainDirectoryNodeImpl(this); + fRoot.setIsRoot(true); + AVMDAOs.Instance().fAVMNodeDAO.save(fRoot); + VersionRoot versionRoot = new VersionRootImpl(this, + fRoot, + fNextVersionID, + time, + creator, + "Initial Empty Version.", + "Initial Empty Version."); + fNextVersionID++; + AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); + } + + /** + * Setter for hibernate. + * @param id The primary key. + */ + protected void setId(long id) + { + fID = id; + } + + /** + * Get the primary key. + * @return The primary key. + */ + public long getId() + { + return fID; + } + + /** + * Set a new root for this. + * @param root + */ + public void setNewRoot(DirectoryNode root) + { + fRoot = root; + fRoot.setIsRoot(true); + } + + /** + * Snapshot this store. This creates a new version record. + * @return The version id of the new snapshot. + */ + @SuppressWarnings("unchecked") + public int createSnapshot(String tag, String description, Map snapShotMap) + { + VersionRoot lastVersion = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(this); + List layeredEntries = + AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.get(lastVersion); + // Is there no need for a snapshot? + if (!fRoot.getIsNew() && layeredEntries.size() == 0) + { + // So, we set the tag and description fields of the latest version. + if (tag != null || description != null) + { + lastVersion.setTag(tag); + lastVersion.setDescription(description); + } + snapShotMap.put(fName, lastVersion.getVersionID()); + return lastVersion.getVersionID(); + } + snapShotMap.put(fName, fNextVersionID); + // Force copies on all the layered nodes from last snapshot. + for (VersionLayeredNodeEntry entry : layeredEntries) + { + String path = entry.getPath(); + path = path.substring(path.indexOf(':') + 1); + Lookup lookup = lookup(-1, path, false, false); + if (lookup == null) + { + continue; + } + if (lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY && + lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_FILE) + { + continue; + } + if (lookup.getCurrentNode().getIsNew()) + { + continue; + } + String parentName[] = AVMNodeConverter.SplitBase(entry.getPath()); + parentName[0] = parentName[0].substring(parentName[0].indexOf(':') + 1); + lookup = lookupDirectory(-1, parentName[0], true); + DirectoryNode parent = (DirectoryNode)lookup.getCurrentNode(); + AVMNode child = parent.lookupChild(lookup, parentName[1], false); + // TODO This is funky. Need to look carefully to see that this call + // does exactly what's needed. + lookup.add(child, parentName[1], false); + AVMNode newChild = null; + if (child.getType() == AVMNodeType.LAYERED_DIRECTORY) + { + newChild = child.copy(lookup); + } + else + { + newChild = ((LayeredFileNode)child).copyLiterally(lookup); + } + parent.putChild(parentName[1], newChild); + } + // Clear out the new nodes. + List newInRep = AVMDAOs.Instance().fAVMNodeDAO.getNewInStore(this); + List layeredNodes = new ArrayList(); + for (AVMNode newGuy : newInRep) + { + newGuy.setStoreNew(null); + Layered layered = null; + if (newGuy.getType() == AVMNodeType.LAYERED_DIRECTORY && + ((LayeredDirectoryNode)newGuy).getPrimaryIndirection()) + { + layered = (Layered)AVMNodeUnwrapper.Unwrap(newGuy); + } + if (newGuy.getType() == AVMNodeType.LAYERED_FILE) + { + layered = (Layered)AVMNodeUnwrapper.Unwrap(newGuy); + } + if (layered != null) + { + layeredNodes.add(newGuy); + String indirection = layered.getIndirection(); + String storeName = indirection.substring(0, indirection.indexOf(':')); + if (!snapShotMap.containsKey(storeName)) + { + AVMStore store = AVMDAOs.Instance().fAVMStoreDAO.getByName(storeName); + if (store == null) + { + layered.setIndirectionVersion(-1); + } + else + { + store.createSnapshot(null, null, snapShotMap); + layered.setIndirectionVersion(snapShotMap.get(storeName)); + } + } + else + { + layered.setIndirectionVersion(snapShotMap.get(storeName)); + } + } + } + // Make up a new version record. + String user = RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); + if (user == null) + { + user = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); + } + VersionRoot versionRoot = new VersionRootImpl(this, + fRoot, + fNextVersionID, + System.currentTimeMillis(), + user, + tag, + description); + AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); + for (AVMNode node : layeredNodes) + { + List paths = fAVMRepository.getVersionPaths(versionRoot, node); + for (String path : paths) + { + VersionLayeredNodeEntry entry = + new VersionLayeredNodeEntryImpl(versionRoot, path); + AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.save(entry); + } + } + // Increment the version id. + fNextVersionID++; + return fNextVersionID - 1; + } + + /** + * 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, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + AVMNode child = dir.lookupChild(lPath, name, true); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + 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, null); + ((LayeredDirectoryNodeImpl)newDir).setPrimaryIndirection(false); + ((LayeredDirectoryNodeImpl)newDir).setLayerID(lPath.getTopLayer().getLayerID()); + } + else + { + newDir = new PlainDirectoryNodeImpl(this); + } + // newDir.setVersionID(getNextVersionID()); + if (child != null) + { + newDir.setAncestor(child); + } + dir.updateModTime(); + dir.putChild(name, newDir); + } + + /** + * 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, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + dstPath + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + AVMNode child = dir.lookupChild(lPath, name, true); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + LayeredDirectoryNode newDir = + new LayeredDirectoryNodeImpl(srcPath, this, null); + 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(fAVMRepository.issueLayerID()); + } + if (child != null) + { + newDir.setAncestor(child); + } + dir.updateModTime(); + dir.putChild(name, newDir); + // 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. + * initial content. + */ + public OutputStream createFile(String path, String name) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + AVMNode child = dir.lookupChild(lPath, name, true); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + PlainFileNodeImpl file = new PlainFileNodeImpl(this); + // file.setVersionID(getNextVersionID()); + dir.updateModTime(); + dir.putChild(name, file); + if (child != null) + { + file.setAncestor(child); + } + file.setContentData(new ContentData(null, + RawServices.Instance().getMimetypeService().guessMimetype(name), + -1, + "UTF-8")); + ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); + return writer.getContentOutputStream(); + } + + /** + * Create a file with the given contents. + * @param path The path to the containing directory. + * @param name The name to give the new file. + * @param data The contents. + */ + public void createFile(String path, String name, File data) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + AVMNode child = dir.lookupChild(lPath, name, true); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + PlainFileNodeImpl file = new PlainFileNodeImpl(this); + // file.setVersionID(getNextVersionID()); + dir.updateModTime(); + dir.putChild(name, file); + if (child != null) + { + file.setAncestor(child); + } + file.setContentData(new ContentData(null, + RawServices.Instance().getMimetypeService().guessMimetype(name), + -1, + "UTF-8")); + ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); + writer.putContent(data); + } + + /** + * 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, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + dstPath + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + AVMNode child = dir.lookupChild(lPath, name, true); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + // TODO Reexamine decision to not check validity of srcPath. + LayeredFileNodeImpl newFile = + new LayeredFileNodeImpl(srcPath, this); + if (child != null) + { + newFile.setAncestor(child); + } + dir.updateModTime(); + dir.putChild(name, newFile); + // 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) + { + ContentReader reader = getContentReader(version, path); + if (reader == null) + { + // TODO This is wrong, wrong, wrong. Do something about it + // sooner rather than later. + throw new AVMNotFoundException(path + " has no content."); + } + return reader.getContentInputStream(); + } + + /** + * Get a ContentReader from a file. + * @param version The version to look under. + * @param path The path to the file. + * @return A ContentReader. + */ + public ContentReader getContentReader(int version, String path) + { + NodeRef nodeRef = AVMNodeConverter.ToNodeRef(version, fName + ":" + path); + return RawServices.Instance().getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT); + } + + /** + * Get a ContentWriter to a file. + * @param path The path to the file. + * @return A ContentWriter. + */ + public ContentWriter createContentWriter(String path) + { + NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, fName + ":" + path); + ContentWriter writer = + RawServices.Instance().getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + return writer; + } + + /** + * 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 SortedMap getListing(int version, String path, + boolean includeDeleted) + { + Lookup lPath = lookupDirectory(version, path, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + Map listing = dir.getListing(lPath, includeDeleted); + return translateListing(listing, lPath); + } + + /** + * Get the list of nodes directly contained in a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @return A Map of names to descriptors. + */ + public SortedMap getListingDirect(int version, String path, + boolean includeDeleted) + { + Lookup lPath = lookupDirectory(version, path, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (lPath.isLayered() && dir.getType() != AVMNodeType.LAYERED_DIRECTORY) + { + return new TreeMap(); + } + Map listing = dir.getListingDirect(lPath, includeDeleted); + return translateListing(listing, lPath); + } + + /** + * Helper to convert an internal representation of a directory listing + * to an external representation. + * @param listing The internal listing, a Map of names to nodes. + * @param lPath The Lookup for the directory. + * @return A Map of names to descriptors. + */ + private SortedMap + translateListing(Map listing, Lookup lPath) + { + SortedMap results = new TreeMap(); + for (String name : listing.keySet()) + { + // TODO consider doing this at a lower level. + AVMNode child = AVMNodeUnwrapper.Unwrap(listing.get(name)); + AVMNodeDescriptor desc = child.getDescriptor(lPath, name); + results.put(name, desc); + } + return results; + } + + /** + * Get the names of the deleted nodes in a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @return A List of names. + */ + public List getDeleted(int version, String path) + { + Lookup lPath = lookupDirectory(version, path, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + return dir.getDeletedNames(); + } + + /** + * Get an output stream to a file. + * @param path The path to the file. + * @return An OutputStream. + */ + public OutputStream getOutputStream(String path) + { + ContentWriter writer = createContentWriter(path); + return writer.getContentOutputStream(); + } + + /** + * 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) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (dir.lookupChild(lPath, name, false) == null) + { + throw new AVMNotFoundException("Does not exist: " + name); + } + dir.removeChild(lPath, name); + dir.updateModTime(); + } + + /** + * 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, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + dirPath + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.LAYERED_DIRECTORY) + { + throw new AVMWrongTypeException("Not a layered directory: " + dirPath); + } + ((LayeredDirectoryNode)node).uncover(lPath, name); + node.updateModTime(); + } + + // 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 versions for this AVMStore. + * @return A Set of version ids. + */ + @SuppressWarnings("unchecked") + public List getVersions() + { + List versions = AVMDAOs.Instance().fVersionRootDAO.getAllInAVMStore(this); + List descs = new ArrayList(); + for (VersionRoot vr : versions) + { + VersionDescriptor desc = + new VersionDescriptor(fName, + vr.getVersionID(), + vr.getCreator(), + vr.getCreateDate(), + vr.getTag(), + vr.getDescription()); + descs.add(desc); + } + return descs; + } + + /** + * Get the versions between the given dates (inclusive). From or + * to may be null but not both. + * @param from The earliest date. + * @param to The latest date. + * @return The Set of matching version IDs. + */ + @SuppressWarnings("unchecked") + public List getVersions(Date from, Date to) + { + List versions = AVMDAOs.Instance().fVersionRootDAO.getByDates(this, from, to); + List descs = new ArrayList(); + for (VersionRoot vr : versions) + { + VersionDescriptor desc = + new VersionDescriptor(fName, + vr.getVersionID(), + vr.getCreator(), + vr.getCreateDate(), + vr.getTag(), + vr.getDescription()); + descs.add(desc); + } + return descs; + } + + /** + * Get the AVMRepository. + * @return The AVMRepository + */ + public AVMRepository getAVMRepository() + { + return fAVMRepository; + } + + /** + * Lookup up a path. + * @param version The version to look in. + * @param path The path to look up. + * @param write Whether this is in the context of a write. + * @return A Lookup object. + */ + public Lookup lookup(int version, String path, boolean write, boolean includeDeleted) + { + SimplePath sPath = new SimplePath(path); + return RawServices.Instance().getLookupCache().lookup(this, version, sPath, write, includeDeleted); + } + + /** + * 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 + { + root = AVMDAOs.Instance().fAVMNodeDAO.getAVMStoreRoot(this, version); + } + return root.getDescriptor(fName + ":", "", null, -1); + } + + /** + * Lookup a node and insist that it is a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @param write Whether this is in a write context. + * @return A Lookup object. + */ + public Lookup lookupDirectory(int version, String path, boolean write) + { + // Just do a regular lookup and assert that the last element + // is a directory. + Lookup lPath = lookup(version, path, write, false); + if (lPath == null) + { + return null; + } + if (lPath.getCurrentNode().getType() != AVMNodeType.PLAIN_DIRECTORY && + lPath.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY) + { + return null; + } + 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, false, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + if (!lPath.isLayered()) + { + return null; + } + AVMNode node = lPath.getCurrentNode(); + if (node.getType() == AVMNodeType.LAYERED_DIRECTORY) + { + LayeredDirectoryNode dir = (LayeredDirectoryNode)node; + return dir.getUnderlying(lPath); + } + else if (node.getType() == AVMNodeType.LAYERED_FILE) + { + LayeredFileNode file = (LayeredFileNode)node; + return file.getUnderlying(lPath); + } + return lPath.getIndirectionPath(); + } + + /** + * Make the indicated node a primary indirection. + * @param path The path to the node. + */ + public void makePrimary(String path) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!lPath.isLayered()) + { + throw new AVMException("Not in a layered context: " + path); + } + dir.turnPrimary(lPath); + dir.updateModTime(); + } + + /** + * 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, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!lPath.isLayered()) + { + throw new AVMException("Not in a layered context: " + path); + } + dir.retarget(lPath, target); + dir.updateModTime(); + } + + /** + * Set the name of this AVMStore. + * @param name + */ + public void setName(String name) + { + fName = name; + } + + /** + * Get the name of this AVMStore. + * @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; + } + + /** + * This gets the last extant version id. + */ + public int getLastVersionID() + { + return AVMDAOs.Instance().fVersionRootDAO.getMaxVersionID(this); + } + + /** + * 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; + } + + /** + * Equals override. + * @param obj + * @return Equality. + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof AVMStore)) + { + return false; + } + return fID == ((AVMStore)obj).getId(); + } + + /** + * Get a hash code. + * @return The hash code. + */ + @Override + public int hashCode() + { + return (int)fID; + } + + /** + * 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"); + } + VersionRoot vRoot = AVMDAOs.Instance().fVersionRootDAO.getByVersionID(this, version); + if (vRoot == null) + { + throw new AVMNotFoundException("Version not found."); + } + AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.delete(vRoot); + AVMNode root = vRoot.getRoot(); + root.setIsRoot(false); + AVMDAOs.Instance().fAVMNodeDAO.update(root); + AVMDAOs.Instance().fVersionRootDAO.delete(vRoot); + if (root.equals(fRoot)) + { + // We have to set a new current root. + // TODO More hibernate goofiness to compensate for: fSuper.getSession().flush(); + vRoot = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(this); + fRoot = vRoot.getRoot(); + AVMDAOs.Instance().fAVMStoreDAO.update(this); + } + } + + /** + * Get the descriptor for this. + * @return An AVMStoreDescriptor + */ + public AVMStoreDescriptor getDescriptor() + { + return new AVMStoreDescriptor(fName, + getProperty(ContentModel.PROP_CREATOR).getStringValue(), + ((Date)getProperty(ContentModel.PROP_CREATED).getValue(DataTypeDefinition.DATE)).getTime()); + } + + /** + * Set the opacity of a layered directory. An opaque directory hides + * what is pointed at by its indirection. + * @param path The path to the layered directory. + * @param opacity True is opaque; false is not. + */ + public void setOpacity(String path, boolean opacity) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof LayeredDirectoryNode)) + { + throw new AVMWrongTypeException("Not a LayeredDirectoryNode."); + } + ((LayeredDirectoryNode)node).setOpacity(opacity); + node.updateModTime(); + } + + // TODO Does it make sense to set properties on DeletedNodes? + /** + * Set a property on a node. + * @param path The path to the node. + * @param name The name of the property. + * @param value The value to set. + */ + public void setNodeProperty(String path, QName name, PropertyValue value) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + node.setProperty(name, value); + node.setGuid(GUID.generate()); + } + + /** + * Set a collection of properties on a node. + * @param path The path to the node. + * @param properties The Map of QNames to PropertyValues. + */ + public void setNodeProperties(String path, Map properties) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + node.setProperties(properties); + node.setGuid(GUID.generate()); + } + + /** + * Get a property by name. + * @param version The version to lookup. + * @param path The path to the node. + * @param name The name of the property. + * @return A PropertyValue or null if not found. + */ + public PropertyValue getNodeProperty(int version, String path, QName name) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + return node.getProperty(name); + } + + /** + * Get all the properties associated with a node. + * @param version The version to lookup. + * @param path The path to the node. + * @return A Map of QNames to PropertyValues. + */ + public Map getNodeProperties(int version, String path) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + return node.getProperties(); + } + + /** + * Delete a single property from a node. + * @param path The path to the node. + * @param name The name of the property. + */ + public void deleteNodeProperty(String path, QName name) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + node.setGuid(GUID.generate()); + node.deleteProperty(name); + } + + /** + * Delete all properties from a node. + * @param path The path to the node. + */ + public void deleteNodeProperties(String path) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + node.setGuid(GUID.generate()); + node.deleteProperties(); + } + + /** + * Set a property on this store. Replaces if property already exists. + * @param name The QName of the property. + * @param value The actual PropertyValue. + */ + public void setProperty(QName name, PropertyValue value) + { + AVMStoreProperty prop = new AVMStorePropertyImpl(); + prop.setStore(this); + prop.setName(name); + prop.setValue(value); + AVMDAOs.Instance().fAVMStorePropertyDAO.save(prop); + } + + /** + * Set a group of properties on this store. Replaces any property that exists. + * @param properties A Map of QNames to PropertyValues to set. + */ + public void setProperties(Map properties) + { + for (QName name : properties.keySet()) + { + setProperty(name, properties.get(name)); + } + } + + /** + * Get a property by name. + * @param name The QName of the property to fetch. + * @return The PropertyValue or null if non-existent. + */ + public PropertyValue getProperty(QName name) + { + AVMStoreProperty prop = AVMDAOs.Instance().fAVMStorePropertyDAO.get(this, name); + if (prop == null) + { + return null; + } + return prop.getValue(); + } + + /** + * Get all the properties associated with this node. + * @return A Map of the properties. + */ + public Map getProperties() + { + List props = + AVMDAOs.Instance().fAVMStorePropertyDAO.get(this); + Map retVal = new HashMap(); + for (AVMStoreProperty prop : props) + { + retVal.put(prop.getName(), prop.getValue()); + } + return retVal; + } + + /** + * Delete a property. + * @param name The name of the property to delete. + */ + public void deleteProperty(QName name) + { + AVMDAOs.Instance().fAVMStorePropertyDAO.delete(this, name); + } + + /** + * Get the ContentData on a file. + * @param version The version to look under. + * @param path The path to the file. + * @return The ContentData corresponding to the file. + */ + public ContentData getContentDataForRead(int version, String path) + { + Lookup lPath = lookup(version, path, false, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof FileNode)) + { + throw new AVMWrongTypeException("File Expected."); + } + return ((FileNode)node).getContentData(lPath); + } + + /** + * Get the ContentData on a file for writing. + * @param path The path to the file. + * @return The ContentData corresponding to the file. + */ + public ContentData getContentDataForWrite(String path) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof FileNode)) + { + throw new AVMWrongTypeException("File Expected."); + } + node.updateModTime(); + node.setGuid(GUID.generate()); + return ((FileNode)node).getContentData(lPath); + } + + /** + * Set the ContentData for a file. + * @param path The path to the file. + * @param data The ContentData to set. + */ + public void setContentData(String path, ContentData data) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof FileNode)) + { + throw new AVMWrongTypeException("File Expected."); + } + ((FileNode)node).setContentData(data); + } + + /** + * Set meta data, aspects, properties, acls, from another node. + * @param path The path to the node to set metadata on. + * @param from The node to get the metadata from. + */ + public void setMetaDataFrom(String path, AVMNode from) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path not found: " + path); + } + AVMNode node = lPath.getCurrentNode(); + node.copyMetaDataFrom(from); + node.setGuid(GUID.generate()); + } + + /** + * Add an aspect to a node. + * @param path The path to the node. + * @param aspectName The name of the aspect. + */ + public void addAspect(String path, QName aspectName) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (AVMDAOs.Instance().fAVMAspectNameDAO.exists(node, aspectName)) + { + throw new AVMExistsException("Aspect exists."); + } + AVMAspectName newName = + new AVMAspectNameImpl(); + newName.setNode(node); + newName.setName(aspectName); + node.setGuid(GUID.generate()); + AVMDAOs.Instance().fAVMAspectNameDAO.save(newName); + } + + /** + * Get all aspects on a given node. + * @param version The version to look under. + * @param path The path to the node. + * @return A List of the QNames of the aspects. + */ + public List getAspects(int version, String path) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + List names = + AVMDAOs.Instance().fAVMAspectNameDAO.get(node); + ArrayList result = new ArrayList(); + for (AVMAspectName name : names) + { + result.add(name.getName()); + } + return result; + } + + /** + * Remove an aspect and all its properties from a node. + * @param path The path to the node. + * @param aspectName The name of the aspect. + */ + public void removeAspect(String path, QName aspectName) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + AVMDAOs.Instance().fAVMAspectNameDAO.delete(node, aspectName); + AspectDefinition def = RawServices.Instance().getDictionaryService().getAspect(aspectName); + Map properties = + def.getProperties(); + for (QName name : properties.keySet()) + { + AVMDAOs.Instance().fAVMNodePropertyDAO.delete(node, name); + } + node.setGuid(GUID.generate()); + } + + /** + * Does a given node have a given aspect. + * @param version The version to look under. + * @param path The path to the node. + * @param aspectName The name of the aspect. + * @return Whether the node has the aspect. + */ + public boolean hasAspect(int version, String path, QName aspectName) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + return AVMDAOs.Instance().fAVMAspectNameDAO.exists(node, aspectName); + } + + /** + * Set the ACL on a node. + * @param path The path to the node. + * @param acl The ACL to set. + */ + public void setACL(String path, DbAccessControlList acl) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + node.setAcl(acl); + node.setGuid(GUID.generate()); + } + + /** + * Get the ACL on a node. + * @param version The version to look under. + * @param path The path to the node. + * @return The ACL. + */ + public DbAccessControlList getACL(int version, String path) + { + Lookup lPath = lookup(version, path, false, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + return lPath.getCurrentNode().getAcl(); + } + + /** + * Link a node intro a directory, directly. + * @param parentPath The path to the directory. + * @param name The name to give the parent. + * @param toLink The node to link. + */ + public void link(String parentPath, String name, AVMNodeDescriptor toLink) + { + Lookup lPath = lookupDirectory(-1, parentPath, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + parentPath + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + dir.link(lPath, name, toLink); + } + + /** + * Revert a head path to a given version. This works by cloning + * the version to revert to, and then linking that new version into head. + * The reverted version will have the previous head version as ancestor. + * @param path The path to the parent directory. + * @param name The name of the node to revert. + * @param toRevertTo The descriptor of the version to revert to. + */ + public void revert(String path, String name, AVMNodeDescriptor toRevertTo) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + AVMNode child = dir.lookupChild(lPath, name, true); + if (child == null) + { + throw new AVMNotFoundException("Node not found: " + name); + } + AVMNode revertNode = AVMDAOs.Instance().fAVMNodeDAO.getByID(toRevertTo.getId()); + if (revertNode == null) + { + throw new AVMNotFoundException(toRevertTo.toString()); + } + AVMNode toLink = revertNode.copy(lPath); + dir.putChild(name, toLink); + toLink.changeAncestor(child); + toLink.setVersionID(child.getVersionID() + 1); + if (AVMDAOs.Instance().fAVMAspectNameDAO.exists(toLink, WCMModel.ASPECT_REVERTED)) + { + AVMDAOs.Instance().fAVMAspectNameDAO.delete(toLink, WCMModel.ASPECT_REVERTED); + } + AVMAspectName aspect = new AVMAspectNameImpl(); + aspect.setNode(toLink); + aspect.setName(WCMModel.ASPECT_REVERTED); + AVMDAOs.Instance().fAVMAspectNameDAO.save(aspect); + PropertyValue value = new PropertyValue(null, toRevertTo.getId()); + toLink.setProperty(WCMModel.PROP_REVERTED_ID, value); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMStore#setGuid(java.lang.String, java.lang.String) + */ + public void setGuid(String path, String guid) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path not found: " + path); + } + AVMNode node = lPath.getCurrentNode(); + node.setGuid(guid); + } +} diff --git a/source/java/org/alfresco/sandbox/SandboxConstants.java b/source/java/org/alfresco/sandbox/SandboxConstants.java index fa3f43a816..39947b0045 100644 --- a/source/java/org/alfresco/sandbox/SandboxConstants.java +++ b/source/java/org/alfresco/sandbox/SandboxConstants.java @@ -38,11 +38,15 @@ public class SandboxConstants public final static String PROP_SANDBOXID = ".sandbox-id."; public final static String PROP_DNS = ".dns."; public final static String PROP_SANDBOX_STORE_PREFIX = ".sandbox.store."; - public final static QName PROP_SANDBOX_STAGING_MAIN = QName.createQName(null, ".sandbox.staging.main"); - public final static QName PROP_SANDBOX_STAGING_PREVIEW = QName.createQName(null, ".sandbox.staging.preview"); - public final static QName PROP_SANDBOX_AUTHOR_MAIN = QName.createQName(null, ".sandbox.author.main"); - public final static QName PROP_SANDBOX_AUTHOR_PREVIEW = QName.createQName(null, ".sandbox.author.preview"); - public final static QName PROP_SANDBOX_WORKFLOW_MAIN = QName.createQName(null, ".sandbox.workflow.main"); - public final static QName PROP_SANDBOX_WORKFLOW_PREVIEW = QName.createQName(null, ".sandbox.workflow.preview"); - public final static QName PROP_WEBSITE_NAME = QName.createQName(null, ".website.name"); + + public final static QName PROP_SANDBOX_STAGING_MAIN = QName.createQName(null, ".sandbox.staging.main"); + public final static QName PROP_SANDBOX_STAGING_PREVIEW = QName.createQName(null, ".sandbox.staging.preview"); + public final static QName PROP_SANDBOX_AUTHOR_MAIN = QName.createQName(null, ".sandbox.author.main"); + public final static QName PROP_SANDBOX_AUTHOR_PREVIEW = QName.createQName(null, ".sandbox.author.preview"); + public final static QName PROP_SANDBOX_WORKFLOW_MAIN = QName.createQName(null, ".sandbox.workflow.main"); + public final static QName PROP_SANDBOX_WORKFLOW_PREVIEW = QName.createQName(null, ".sandbox.workflow.preview"); + public final static QName PROP_SANDBOX_AUTHOR_WORKFLOW_MAIN = QName.createQName(null, ".sandbox.author.workflow.main"); + public final static QName PROP_SANDBOX_AUTHOR_WORKFLOW_PREVIEW = QName.createQName(null, ".sandbox.author.workflow.preview"); + public final static QName PROP_WEBSITE_NAME = QName.createQName(null, ".website.name"); + public final static QName PROP_AUTHOR_NAME = QName.createQName(null, ".author.name"); }