/* * Copyright (C) 2005-2009 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.wcm.sandbox; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.mbeans.VirtServerRegistry; import org.alfresco.model.WCMAppModel; import org.alfresco.model.WCMWorkflowModel; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.avm.actions.AVMRevertStoreAction; import org.alfresco.repo.avm.actions.AVMUndoSandboxListAction; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.VersionDescriptor; import org.alfresco.service.cmr.avmsync.AVMDifference; 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.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.NameMatcher; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.alfresco.util.VirtServerUtils; import org.alfresco.wcm.asset.AssetInfo; import org.alfresco.wcm.asset.AssetInfoImpl; import org.alfresco.wcm.asset.AssetService; import org.alfresco.wcm.util.WCMUtil; import org.alfresco.wcm.util.WCMWorkflowUtil; import org.alfresco.wcm.webproject.WebProjectInfo; import org.alfresco.wcm.webproject.WebProjectService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Sandbox Service fundamental API. *

* This service API is designed to support the public facing Sandbox APIs. * * @author janv */ public class SandboxServiceImpl implements SandboxService { /** Logger */ private static Log logger = LogFactory.getLog(SandboxServiceImpl.class); private static final String WORKFLOW_SUBMITDIRECT = "jbpm$wcmwf:submitdirect"; private WebProjectService wpService; private SandboxFactory sandboxFactory; private AVMService avmService; private AVMSyncService avmSyncService; private NameMatcher nameMatcher; private VirtServerRegistry virtServerRegistry; private ActionService actionService; private WorkflowService workflowService; private AssetService assetService; private TransactionService transactionService; public void setWebProjectService(WebProjectService wpService) { this.wpService = wpService; } public void setSandboxFactory(SandboxFactory sandboxFactory) { this.sandboxFactory = sandboxFactory; } public void setAvmService(AVMService avmService) { this.avmService = avmService; } public void setAvmSyncService(AVMSyncService avmSyncService) { this.avmSyncService = avmSyncService; } public void setNameMatcher(NameMatcher nameMatcher) { this.nameMatcher = nameMatcher; } public void setVirtServerRegistry(VirtServerRegistry virtServerRegistry) { this.virtServerRegistry = virtServerRegistry; } public void setActionService(ActionService actionService) { this.actionService = actionService; } public void setWorkflowService(WorkflowService workflowService) { this.workflowService = workflowService; } public void setAssetService(AssetService assetService) { this.assetService = assetService; } public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#createAuthorSandbox(java.lang.String) */ public SandboxInfo createAuthorSandbox(String wpStoreId) { ParameterCheck.mandatoryString("wpStoreId", wpStoreId); String currentUserName = AuthenticationUtil.getRunAsUser(); SandboxInfo sbInfo = null; if (! wpService.isWebUser(wpStoreId, currentUserName)) { throw new AccessDeniedException("Only web project users may create their own (author) sandbox for '"+currentUserName+"' (store id: "+wpStoreId+")"); } else { sbInfo = createAuthorSandboxImpl(wpStoreId, currentUserName); } return sbInfo; } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#createAuthorSandbox(java.lang.String, java.lang.String) */ public SandboxInfo createAuthorSandbox(String wpStoreId, String userName) { ParameterCheck.mandatoryString("wpStoreId", wpStoreId); ParameterCheck.mandatoryString("userName", userName); // is the current user a content manager for this web project ? if (! wpService.isContentManager(wpStoreId)) { throw new AccessDeniedException("Only content managers may create author sandbox for '"+userName+"' (store id: "+wpStoreId+")"); } return createAuthorSandboxImpl(wpStoreId, userName); } private SandboxInfo createAuthorSandboxImpl(String wpStoreId, String userName) { WebProjectInfo wpInfo = wpService.getWebProject(wpStoreId); final NodeRef wpNodeRef = wpInfo.getNodeRef(); final List managers = new ArrayList(4); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { public Object doWork() throws Exception { // retrieve the list of managers from the existing users Map existingUserRoles = wpService.listWebUsers(wpNodeRef); for (Map.Entry userRole : existingUserRoles.entrySet()) { String username = userRole.getKey(); String userrole = userRole.getValue(); if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) { managers.add(username); } } return null; } }, AuthenticationUtil.getSystemUserName()); String role = wpService.getWebUserRole(wpNodeRef, userName); SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userName, role); List sandboxInfoList = new LinkedList(); sandboxInfoList.add(sbInfo); // Bind the post-commit transaction listener with data required for virtualization server notification CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, wpService.listWebApps(wpNodeRef)); AlfrescoTransactionSupport.bindListener(tl); if (logger.isInfoEnabled()) { logger.info("Created author sandbox: " + sbInfo.getSandboxId() + " (web project id: " + wpStoreId + ")"); } return sbInfo; } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listSandboxes(java.lang.String) */ public List listSandboxes(String wpStoreId) { ParameterCheck.mandatoryString("wpStoreId", wpStoreId); String currentUser = AuthenticationUtil.getRunAsUser(); List sbInfos = null; if (wpService.isContentManager(wpStoreId, currentUser)) { sbInfos = sandboxFactory.listAllSandboxes(wpStoreId); } else { sbInfos = new ArrayList(1); SandboxInfo authorSandbox = getAuthorSandbox(wpStoreId, currentUser); if (authorSandbox != null) { sbInfos.add(authorSandbox); } sbInfos.add(getSandbox(WCMUtil.buildStagingStoreName(wpStoreId))); // get staging sandbox } return sbInfos; } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listSandboxes(java.lang.String, java.lang.String) */ public List listSandboxes(final String wpStoreId, String userName) { ParameterCheck.mandatoryString("wpStoreId", wpStoreId); ParameterCheck.mandatoryString("userName", userName); if (! wpService.isContentManager(wpStoreId)) { throw new AccessDeniedException("Only content managers may list sandboxes for '"+userName+"' (web project id: "+wpStoreId+")"); } return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() { public List doWork() throws Exception { return listSandboxes(wpStoreId); } }, userName); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#isSandboxType(java.lang.String, org.alfresco.service.namespace.QName) */ public boolean isSandboxType(String sbStoreId, QName sandboxType) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatory("sandboxType", sandboxType); SandboxInfo sbInfo = getSandbox(sbStoreId); if (sbInfo != null) { return sbInfo.getSandboxType().equals(sandboxType); } return false; } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#getSandbox(java.lang.String) */ public SandboxInfo getSandbox(String sbStoreId) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); // check user has read access to web project (ie. is a web user) if (! wpService.isWebUser(wpStoreId)) { return null; } if (! WCMUtil.isStagingStore(sbStoreId)) { String currentUser = AuthenticationUtil.getRunAsUser(); if (! ((WCMUtil.getUserName(sbStoreId).equals(currentUser)) || (wpService.isContentManager(wpStoreId, currentUser)))) { throw new AccessDeniedException("Only content managers may get sandbox '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } } return sandboxFactory.getSandbox(sbStoreId); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#getAuthorSandbox(java.lang.String) */ public SandboxInfo getAuthorSandbox(String wpStoreId) { ParameterCheck.mandatoryString("wpStoreId", wpStoreId); String currentUserName = AuthenticationUtil.getRunAsUser(); return getSandbox(WCMUtil.buildUserMainStoreName(WCMUtil.buildStagingStoreName(wpStoreId), currentUserName)); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#getUserSandbox(java.lang.String, java.lang.String) */ public SandboxInfo getAuthorSandbox(String wpStoreId, String userName) { ParameterCheck.mandatoryString("wpStoreId", wpStoreId); ParameterCheck.mandatoryString("userName", userName); return getSandbox(WCMUtil.buildUserMainStoreName(WCMUtil.buildStagingStoreName(wpStoreId), userName)); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#getStagingSandbox(java.lang.String) */ public SandboxInfo getStagingSandbox(String wpStoreId) { ParameterCheck.mandatoryString("wpStoreId", wpStoreId); return getSandbox(WCMUtil.buildStagingStoreName(wpStoreId)); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#deleteSandbox(java.lang.String) */ public void deleteSandbox(String sbStoreId) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); String currentUserName = AuthenticationUtil.getRunAsUser(); if (sbStoreId.equals(WCMUtil.buildUserMainStoreName(wpStoreId, currentUserName))) { // author may delete their own sandbox sandboxFactory.deleteSandbox(sbStoreId); } else { if (! wpService.isContentManager(wpStoreId)) { throw new AccessDeniedException("Only content managers may delete sandbox '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } if (sbStoreId.equals(wpStoreId)) { throw new AccessDeniedException("Cannot delete staging sandbox '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } // content manager may delete sandboxes, except staging sandbox sandboxFactory.deleteSandbox(sbStoreId); } if (logger.isInfoEnabled()) { logger.info("Deleted sandbox: " + sbStoreId + " (web project id: " + wpStoreId + ")"); } } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listChangedAll(java.lang.String, boolean) */ public List listChangedAll(String sbStoreId, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String avmDirectoryPath = WCMUtil.buildSandboxRootPath(sbStoreId); // currently :/www/avm_webapps return listChanged(sbStoreId, WCMUtil.getStoreRelativePath(avmDirectoryPath), includeDeleted); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listChangedWebApp(java.lang.String, java.lang.String, boolean) */ public List listChangedWebApp(String sbStoreId, String webApp, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("webApp", webApp); // filter by current webapp String avmDirectoryPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp); return listChanged(sbStoreId, WCMUtil.getStoreRelativePath(avmDirectoryPath), includeDeleted); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listChanged(java.lang.String, java.lang.String, boolean) */ public List listChanged(String sbStoreId, String relativePath, boolean includeDeleted) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("relativePath", relativePath); // TODO - allow list for any sandbox if (! WCMUtil.isUserStore(sbStoreId)) { throw new AlfrescoRuntimeException("Not an author sandbox: "+sbStoreId); } // build the paths to the stores to compare - filter by given directory path String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); String stagingSandboxId = WCMUtil.buildStagingStoreName(wpStoreId); return listChanged(sbStoreId, relativePath, stagingSandboxId, relativePath, includeDeleted); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listChanged(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) */ public List listChanged(String srcSandboxStoreId, String srcRelativePath, String dstSandboxStoreId, String dstRelativePath, boolean includeDeleted) { ParameterCheck.mandatoryString("srcSandboxStoreId", srcSandboxStoreId); ParameterCheck.mandatoryString("srcRelativePath", srcRelativePath); ParameterCheck.mandatoryString("dstSandboxStoreId", dstSandboxStoreId); ParameterCheck.mandatoryString("dstRelativePath", dstRelativePath); // checks sandbox access (TODO review) getSandbox(srcSandboxStoreId); // ignore result getSandbox(dstSandboxStoreId); // ignore result String avmSrcPath = srcSandboxStoreId + WCMUtil.AVM_STORE_SEPARATOR + srcRelativePath; String avmDstPath = dstSandboxStoreId + WCMUtil.AVM_STORE_SEPARATOR + dstRelativePath; return listChanged(-1, avmSrcPath, -1, avmDstPath, includeDeleted); } private List listChanged(int srcVersion, String srcPath, int dstVersion, String dstPath, boolean includeDeleted) { long start = System.currentTimeMillis(); List diffs = avmSyncService.compare(srcVersion, srcPath, dstVersion, dstPath, nameMatcher); List assets = new ArrayList(diffs.size()); for (AVMDifference diff : diffs) { // convert each diff record into an AVM node descriptor String sourcePath = diff.getSourcePath(); String[] parts = WCMUtil.splitPath(sourcePath); AssetInfo asset = assetService.getAsset(parts[0], -1, parts[1], includeDeleted); if (asset != null) { // TODO refactor ((AssetInfoImpl)asset).setDiffCode(diff.getDifferenceCode()); assets.add(asset); } } if (logger.isTraceEnabled()) { logger.trace("listChanged: "+assets.size()+" assets in "+(System.currentTimeMillis()-start)+" ms (between "+srcVersion+","+srcPath+" and "+dstVersion+","+dstPath); } return assets; } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#submitAll(java.lang.String, java.lang.String, java.lang.String) */ public void submitAll(String sbStoreId, String submitLabel, String submitComment) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String avmDirectoryPath = WCMUtil.buildSandboxRootPath(sbStoreId); // currently :/www/avm_webapps submit(sbStoreId, WCMUtil.getStoreRelativePath(avmDirectoryPath), submitLabel, submitComment); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#submitWebApp(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ public void submitWebApp(String sbStoreId, String webApp, String submitLabel, String submitComment) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("webApp", webApp); String avmDirectoryPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp); submit(sbStoreId, WCMUtil.getStoreRelativePath(avmDirectoryPath), submitLabel, submitComment); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#submit(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ public void submit(String sbStoreId, String relativePath, String submitLabel, String submitComment) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("relativePath", relativePath); List assets = listChanged(sbStoreId, relativePath, true); submitListAssets(sbStoreId, assets, submitLabel, submitComment); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#submitList(java.lang.String, java.util.List, java.lang.String, java.lang.String) */ public void submitList(String sbStoreId, List relativePaths, String submitLabel, String submitComment) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); List assets = new ArrayList(relativePaths.size()); for (String relativePath : relativePaths) { // convert each path into an asset AssetInfo asset = assetService.getAsset(sbStoreId, -1, relativePath, true); if (asset != null) { assets.add(asset); } } submitListAssets(sbStoreId, assets, submitLabel, submitComment); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#submitListAssets(java.lang.String, java.util.List, java.lang.String, java.lang.String) */ public void submitListAssets(String sbStoreId, List assets, String submitLabel, String submitComment) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("submitLabel", submitLabel); // TODO - consider submit to higher-level sandbox, not just to staging if (! WCMUtil.isUserStore(sbStoreId)) { throw new AlfrescoRuntimeException("Not an author sandbox: "+sbStoreId); } List relativePaths = new ArrayList(assets.size()); for (AssetInfo asset : assets) { relativePaths.add(asset.getPath()); } // via submit direct workflow submitViaWorkflow(sbStoreId, relativePaths, null, null, submitLabel, submitComment, null, null, false, false); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#submitListAssets(java.lang.String, java.util.List, java.lang.String, java.util.Map, java.lang.String, java.lang.String, java.util.Map, java.util.Date, boolean, boolean) */ public void submitListAssets(String sbStoreId, List relativePaths, String workflowName, Map workflowParams, String submitLabel, String submitComment, Map expirationDates, Date launchDate, boolean validateLinks, boolean autoDeploy) { // via selected workflow submitViaWorkflow(sbStoreId, relativePaths, workflowName, workflowParams, submitLabel, submitComment, expirationDates, launchDate, validateLinks, autoDeploy); } /** * Submits the selected items via the configured workflow. *

* This method uses 2 separate transactions to perform the submit. * The first one creates the workflow sandbox. The virtualisation * server is then informed of the new stores. The second * transaction then starts the appropriate workflow. This approach * is needed to allow link validation to be performed on the * workflow sandbox. */ private void submitViaWorkflow(final String sbStoreId, final List relativePaths, String workflowName, Map workflowParams, final String submitLabel, final String submitComment, final Map expirationDates, final Date launchDate, final boolean validateLinks, final boolean autoDeploy) { // checks sandbox access (TODO review) getSandbox(sbStoreId); // ignore result final String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); final String stagingSandboxId = WCMUtil.buildStagingStoreName(wpStoreId); final String finalWorkflowName; final Map finalWorkflowParams; if ((workflowName == null) || (workflowName.equals(""))) { finalWorkflowName = WORKFLOW_SUBMITDIRECT; finalWorkflowParams = new HashMap(); } else { finalWorkflowName = workflowName; finalWorkflowParams = workflowParams; } RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); final List srcPaths = new ArrayList(relativePaths.size()); String derivedWebApp = null; boolean multiWebAppsFound = false; for (String relativePath : relativePaths) { // Example srcPath: // mysite--alice:/www/avm_webapps/ROOT/foo.txt String srcPath = sbStoreId + WCMUtil.AVM_STORE_SEPARATOR + relativePath; srcPaths.add(srcPath); // derive webapp for now (TODO check usage) String srcWebApp = WCMUtil.getWebapp(srcPath); if (srcWebApp != null) { if (derivedWebApp == null) { derivedWebApp = srcWebApp; } else if (! derivedWebApp.equals(srcWebApp)) { multiWebAppsFound = true; } } } final String webApp = (multiWebAppsFound == false ? derivedWebApp : null); RetryingTransactionCallback> sandboxCallback = new RetryingTransactionCallback>() { public Pair execute() throws Throwable { // call the actual implementation return createWorkflowSandbox(finalWorkflowName, finalWorkflowParams, stagingSandboxId, srcPaths, expirationDates); } }; // create the workflow sandbox firstly final Pair workflowInfo = txnHelper.doInTransaction(sandboxCallback, false, true); if (workflowInfo != null) { final SandboxInfo wfSandboxInfo = workflowInfo.getFirst(); String virtUpdatePath = workflowInfo.getSecond(); // inform the virtualisation server if the workflow sandbox was created if (virtUpdatePath != null) { WCMUtil.updateVServerWebapp(virtServerRegistry, virtUpdatePath, true); } try { RetryingTransactionCallback workflowCallback = new RetryingTransactionCallback() { public String execute() throws Throwable { // call the actual implementation startWorkflow(wpStoreId, sbStoreId, wfSandboxInfo, webApp, finalWorkflowName, finalWorkflowParams, submitLabel, submitComment, launchDate, validateLinks, autoDeploy); return null; } }; // start the workflow txnHelper.doInTransaction(workflowCallback, false, true); } catch (Throwable err) { cleanupWorkflowSandbox(wfSandboxInfo); throw new AlfrescoRuntimeException("Failed to submit to workflow", err); } } } /** * Creates a workflow sandbox for all the submitted items * * @param context Faces context */ protected Pair createWorkflowSandbox(String workflowName, Map workflowParams, String stagingSandboxId, final List srcPaths, Map expirationDates) { // The virtualization server might need to be notified // because one or more of the files submitted could alter // the behavior the virtual webapp in the target of the submit. // For example, the user might be submitting a new jar or web.xml file. // // This must take place after the transaction has been completed; // therefore, a variable is needed to store the path to the // updated webapp so it can happen in doPostCommitProcessing. String virtUpdatePath = null; SandboxInfo sandboxInfo = null; // create container for our avm workflow package if (! workflowName.equals(WORKFLOW_SUBMITDIRECT)) { // Create workflow sandbox for workflow package sandboxInfo = sandboxFactory.createWorkflowSandbox(stagingSandboxId); } else { // default to direct submit workflow // NOTE: read only workflow sandbox is lighter to construct than full workflow sandbox sandboxInfo = sandboxFactory.createReadOnlyWorkflowSandbox(stagingSandboxId); } // Example workflow main store name: // mysite--workflow-9161f640-b020-11db-8015-130bf9b5b652 String workflowMainStoreName = sandboxInfo.getMainStoreName(); List diffs = new ArrayList(srcPaths.size()); // get diff list - also process expiration dates, if any, and set virt svr update path for (String srcPath : srcPaths) { // We *always* want to update virtualization server // when a workflow sandbox is given data in the // context of a submit workflow. Without this, // it would be impossible to see workflow data // in context. The raw operation to create a // workflow sandbox does not notify the virtualization // server that it exists because it's useful to // defer this operation until everything is already // in place; this allows pointlessly fine-grained // notifications to be suppressed (they're expensive). // // Therefore, just derive the name of the webapp // in the workflow sandbox from the 1st item in // the submit list (even if it's not in WEB-INF), // and force the virt server notification after the // transaction has completed via doPostCommitProcessing. if (virtUpdatePath == null) { // The virtUpdatePath looks just like the srcPath // except that it belongs to a the main store of // the workflow sandbox instead of the sandbox // that originated the submit. virtUpdatePath = workflowMainStoreName + srcPath.substring(srcPath.indexOf(':'),srcPath.length()); } if ((expirationDates != null) && (! expirationDates.isEmpty())) { // process the expiration date (if any) processExpirationDate(srcPath, expirationDates); } diffs.add(new AVMDifference(-1, srcPath, -1, WCMUtil.getCorrespondingPath(srcPath, workflowMainStoreName), AVMDifference.NEWER)); } // write changes to layer so files are marked as modified avmSyncService.update(diffs, null, false, false, false, false, null, null); return new Pair(sandboxInfo, virtUpdatePath); } /** * Starts the configured workflow to allow the submitted items to be link * checked and reviewed. */ protected void startWorkflow(String wpStoreId, String sbStoreId, SandboxInfo wfSandboxInfo, String webApp, String workflowName, Map workflowParams, String submitLabel, String submitComment, Date launchDate, boolean validateLinks, boolean autoDeploy) { ParameterCheck.mandatoryString("workflowName", workflowName); ParameterCheck.mandatory("workflowParams", workflowParams); // start the workflow to get access to the start task WorkflowDefinition wfDef = workflowService.getDefinitionByName(workflowName); WorkflowPath path = workflowService.startWorkflow(wfDef.id, null); if (path != null) { // extract the start task List tasks = workflowService.getTasksForWorkflowPath(path.id); if (tasks.size() == 1) { WorkflowTask startTask = tasks.get(0); if (startTask.state == WorkflowTaskState.IN_PROGRESS) { final NodeRef workflowPackage = WCMWorkflowUtil.createWorkflowPackage(workflowService, avmService, wfSandboxInfo); workflowParams.put(WorkflowModel.ASSOC_PACKAGE, workflowPackage); // add submission parameters workflowParams.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, submitComment); workflowParams.put(WCMWorkflowModel.PROP_LABEL, submitLabel); workflowParams.put(WCMWorkflowModel.PROP_FROM_PATH, WCMUtil.buildStoreRootPath(sbStoreId)); workflowParams.put(WCMWorkflowModel.PROP_LAUNCH_DATE, launchDate); workflowParams.put(WCMWorkflowModel.PROP_VALIDATE_LINKS, new Boolean(validateLinks)); workflowParams.put(WCMWorkflowModel.PROP_AUTO_DEPLOY, new Boolean(autoDeploy)); workflowParams.put(WCMWorkflowModel.PROP_WEBAPP, webApp); workflowParams.put(WCMWorkflowModel.ASSOC_WEBPROJECT, wpService.getWebProjectNodeFromStore(wpStoreId)); // update start task with submit parameters workflowService.updateTask(startTask.id, workflowParams, null, null); // end the start task to trigger the first 'proper' task in the workflow workflowService.endTask(startTask.id, null); } } } } /** * Cleans up the workflow sandbox created by the first transaction. This * action is itself preformed in a separate transaction. */ private void cleanupWorkflowSandbox(final SandboxInfo sandboxInfo) { RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); RetryingTransactionCallback callback = new RetryingTransactionCallback() { public String execute() throws Throwable { // call the actual implementation cleanupWorkflowSandboxImpl(sandboxInfo); return null; } }; try { // Execute the cleanup handler txnHelper.doInTransaction(callback); } catch (Throwable e) { // not much we can do now, just log the error to inform admins logger.error("Failed to cleanup workflow sandbox after workflow failure", e); } } /** * Performs the actual deletion of stores in the workflow sandbox. */ private void cleanupWorkflowSandboxImpl(SandboxInfo sandboxInfo) { if (sandboxInfo != null) { String mainWorkflowStore = sandboxInfo.getMainStoreName(); Map matches = avmService.queryStorePropertyKey(mainWorkflowStore, QName.createQName(null, ".sandbox-id%")); QName sandboxID = matches.keySet().iterator().next(); // Get all the stores in the sandbox. Map> stores = avmService.queryStoresPropertyKeys(sandboxID); for (String storeName : stores.keySet()) { avmService.purgeStore(storeName); } } } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#revertAll(java.lang.String) */ public void revertAll(String sbStoreId) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String avmDirectoryPath = WCMUtil.buildSandboxRootPath(sbStoreId); // currently :/www/avm_webapps revert(sbStoreId, WCMUtil.getStoreRelativePath(avmDirectoryPath)); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#revertWebApp(java.lang.String, java.lang.String) */ public void revertWebApp(String sbStoreId, String webApp) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("webApp", webApp); String avmDirectoryPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp); revert(sbStoreId, WCMUtil.getStoreRelativePath(avmDirectoryPath)); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#revertAllDir(java.lang.String, java.lang.String) */ public void revert(String sbStoreId, String relativePath) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); ParameterCheck.mandatoryString("relativePath", relativePath); List assets = listChanged(sbStoreId, relativePath, true); revertListAssets(sbStoreId, assets); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#revertList(java.lang.String, java.util.List) */ public void revertList(String sbStoreId, List relativePaths) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); List assets = new ArrayList(relativePaths.size()); for (String relativePath : relativePaths) { // convert each path into an asset AssetInfo asset = assetService.getAsset(sbStoreId, -1, relativePath, true); if (asset != null) { assets.add(asset); } } revertListAssets(sbStoreId, assets); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#revertListAssets(java.lang.String, java.util.List) */ public void revertListAssets(String sbStoreId, List assets) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); // checks sandbox access (TODO review) getSandbox(sbStoreId); // ignore result List> versionPaths = new ArrayList>(assets.size()); List tasks = null; for (AssetInfo asset : assets) { if (tasks == null) { tasks = WCMWorkflowUtil.getAssociatedTasksForSandbox(workflowService, WCMUtil.getSandboxStoreId(asset.getAvmPath())); } // TODO ... extra lookup ... either return AVMNodeDescriptor or change getAssociatedTasksForNode ... AVMNodeDescriptor node = avmService.lookup(-1, asset.getAvmPath()); if (WCMWorkflowUtil.getAssociatedTasksForNode(avmService, node, tasks).size() == 0) { String revertPath = asset.getAvmPath(); versionPaths.add(new Pair(-1, revertPath)); if (VirtServerUtils.requiresUpdateNotification(revertPath)) { // Bind the post-commit transaction listener with data required for virtualization server notification UpdateSandboxTransactionListener tl = new UpdateSandboxTransactionListener(revertPath); AlfrescoTransactionSupport.bindListener(tl); } } } Map args = new HashMap(1, 1.0f); args.put(AVMUndoSandboxListAction.PARAM_NODE_LIST, (Serializable)versionPaths); Action action = actionService.createAction(AVMUndoSandboxListAction.NAME, args); actionService.executeAction(action, null); // dummy action ref, list passed as action arg } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listSnapshots(java.lang.String, boolean) */ public List listSnapshots(String sbStoreId, boolean includeSystemGenerated) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); if (! wpService.isContentManager(wpStoreId)) { throw new AccessDeniedException("Only content managers may list snapshots '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } List allVersions = avmService.getStoreVersions(sbStoreId); return listSnapshots(allVersions, includeSystemGenerated); } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#listSnapshots(java.lang.String, java.util.Date, java.util.Date, boolean) */ public List listSnapshots(String sbStoreId, Date from, Date to, boolean includeSystemGenerated) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); if (! wpService.isContentManager(wpStoreId)) { throw new AccessDeniedException("Only content managers may list snapshots '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } List versionsToFilter = avmService.getStoreVersions(sbStoreId, from, to); return listSnapshots(versionsToFilter, includeSystemGenerated); } private List listSnapshots(List versionsToFilter, boolean includeSystemGenerated) { List versions = new ArrayList(versionsToFilter.size()); for (int i = versionsToFilter.size() - 1; i >= 0; i--) // reverse order { VersionDescriptor item = versionsToFilter.get(i); // only display snapshots with a valid tag - others are system generated snapshots if ((includeSystemGenerated == true) || ((item.getTag() != null) && (item.getVersionID() != 0))) { versions.add(new SandboxVersionImpl(item)); } } return versions; } /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#revertSnapshot(java.lang.String, int) */ public void revertSnapshot(final String sbStoreId, final int version) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); if (! wpService.isContentManager(wpStoreId)) { throw new AccessDeniedException("Only content managers may revert staging sandbox '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } // do this as system as the staging area has restricted access (and content manager may not have permission to delete children, for example) List diffs = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() { public List doWork() throws Exception { String sandboxPath = WCMUtil.buildSandboxRootPath(sbStoreId); List diffs = avmSyncService.compare(-1, sandboxPath, version, sandboxPath, null); Map args = new HashMap(1, 1.0f); args.put(AVMRevertStoreAction.PARAM_VERSION, version); Action action = actionService.createAction(AVMRevertStoreAction.NAME, args); actionService.executeAction(action, AVMNodeConverter.ToNodeRef(-1, sbStoreId + WCMUtil.AVM_STORE_SEPARATOR + "/")); return diffs; } }, AuthenticationUtil.getSystemUserName()); // See if any of the files being reverted require notification of the virt server, to update the webapp for (AVMDifference diff : diffs) { if (VirtServerUtils.requiresUpdateNotification(diff.getSourcePath())) { // Bind the post-commit transaction listener with data required for virtualization server notification UpdateSandboxTransactionListener tl = new UpdateSandboxTransactionListener(diff.getSourcePath()); AlfrescoTransactionSupport.bindListener(tl); break; } } } /** * Sets up the expiration date for the given source path * * @param srcPath The path to set the expiration date for */ private void processExpirationDate(String srcPath, Map expirationDates) { // if an expiration date has been set for this item we need to // add the expires aspect and the date supplied Date expirationDate = expirationDates.get(srcPath); if (expirationDate == null) { return; } // make sure the aspect is present if (avmService.hasAspect(-1, srcPath, WCMAppModel.ASPECT_EXPIRES) == false) { avmService.addAspect(srcPath, WCMAppModel.ASPECT_EXPIRES); } // set the expiration date avmService.setNodeProperty(srcPath, WCMAppModel.PROP_EXPIRATIONDATE, new PropertyValue(DataTypeDefinition.DATETIME, expirationDate)); if (logger.isDebugEnabled()) { logger.debug("Set expiration date of " + expirationDate + " for " + srcPath); } } /** * Create Sandbox Transaction listener - invoked after commit */ private class CreateSandboxTransactionListener extends TransactionListenerAdapter { private List sandboxInfoList; private List webAppNames; public CreateSandboxTransactionListener(List sandboxInfoList, List webAppNames) { this.sandboxInfoList = sandboxInfoList; this.webAppNames = webAppNames; } /** * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() */ @Override public void afterCommit() { // Handle notification to the virtualization server // (this needs to occur after the sandboxes are created in the main txn) // reload virtualisation server for webapp(s) in this web project for (SandboxInfo sandboxInfo : this.sandboxInfoList) { String newlyInvitedStoreName = WCMUtil.buildStagingStoreName(sandboxInfo.getMainStoreName()); for (String webAppName : webAppNames) { String path = WCMUtil.buildStoreWebappPath(newlyInvitedStoreName, webAppName); WCMUtil.updateVServerWebapp(virtServerRegistry, path, true); } } } } /** * Update Sandbox Transaction listener - invoked after submit or revert */ private class UpdateSandboxTransactionListener extends TransactionListenerAdapter { private String virtUpdatePath; public UpdateSandboxTransactionListener(String virtUpdatePath) { this.virtUpdatePath = virtUpdatePath; } /** * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() */ @Override public void afterCommit() { // The virtualization server might need to be notified // because one or more of the files submitted / reverted could alter // the behavior the virtual webapp in the target of the submit. // For example, the user might be submitting a new jar or web.xml file. // // This must take place after the transaction has been completed; // force an update of the virt server if necessary if (this.virtUpdatePath != null) { WCMUtil.updateVServerWebapp(virtServerRegistry, this.virtUpdatePath, true); } } } }