diff --git a/config/alfresco/avm-services-context.xml b/config/alfresco/avm-services-context.xml index d9d95113a9..bb86fccabb 100644 --- a/config/alfresco/avm-services-context.xml +++ b/config/alfresco/avm-services-context.xml @@ -232,6 +232,9 @@ + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 821ac6a76e..ed4743d86e 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1167,7 +1167,7 @@ alfresco/bootstrap/tagRootCategory.xml - + patch.projectsFolder @@ -1229,4 +1229,22 @@ + + patch.redeploySubmitProcess + patch.redeploySubmitProcess.description + 0 + 117 + 118 + + + + + jbpm + alfresco/workflow/submit_processdefinition.xml + text/xml + + + + + \ No newline at end of file diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 66d5a17029..ec0a2c11e3 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=117 +version.schema=118 diff --git a/config/alfresco/workflow/submit_processdefinition.xml b/config/alfresco/workflow/submit_processdefinition.xml index 96a6ed3ab8..3a5fec514e 100644 --- a/config/alfresco/workflow/submit_processdefinition.xml +++ b/config/alfresco/workflow/submit_processdefinition.xml @@ -216,7 +216,8 @@ - + + @@ -229,7 +230,8 @@ - + + diff --git a/config/alfresco/workflow/wcm-workflow-messages.properties b/config/alfresco/workflow/wcm-workflow-messages.properties index dd566b960f..03ebbbe4f0 100644 --- a/config/alfresco/workflow/wcm-workflow-messages.properties +++ b/config/alfresco/workflow/wcm-workflow-messages.properties @@ -59,6 +59,8 @@ wcmwf_workflowmodel.property.wcmwf_label.title=Submission Label wcmwf_workflowmodel.property.wcmwf_label.description=Label associated with the submission wcmwf_workflowmodel.property.wcmwf_launchDate.title=Launch Date wcmwf_workflowmodel.property.wcmwf_launchDate.description=Date the content in the submission should be committed +wcmwf_workflowmodel.property.wcmwf_autoDeploy.title=Auto Deploy +wcmwf_workflowmodel.property.wcmwf_autoDeploy.description=Whether the submitted changes should be deployed upon approval wcmwf_workflowmodel.property.wcmwf_validateLinks.title=Validate Links wcmwf_workflowmodel.property.wcmwf_validateLinks.description=Whether links should be verified before entering the review cycle wcmwf_workflowmodel.property.wcmwf_webapp.title=Webapp diff --git a/source/java/org/alfresco/repo/avm/AVMRepository.java b/source/java/org/alfresco/repo/avm/AVMRepository.java index a71cb37902..f8b7ea4dc7 100644 --- a/source/java/org/alfresco/repo/avm/AVMRepository.java +++ b/source/java/org/alfresco/repo/avm/AVMRepository.java @@ -50,6 +50,8 @@ import org.alfresco.service.cmr.avm.AVMStoreDescriptor; import org.alfresco.service.cmr.avm.AVMWrongTypeException; import org.alfresco.service.cmr.avm.LayeringDescriptor; import org.alfresco.service.cmr.avm.VersionDescriptor; +import org.alfresco.service.cmr.dictionary.DictionaryService; +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; @@ -59,9 +61,6 @@ import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; /** * This or AVMStore are @@ -117,7 +116,7 @@ public class AVMRepository private PermissionService fPermissionService; - private ApplicationContext fContext; + private DictionaryService fDictionaryService; // A bunch of TransactionListeners that do work for this. @@ -240,6 +239,11 @@ public class AVMRepository fPermissionService = service; } + public void setDictionaryService(DictionaryService service) + { + fDictionaryService = service; + } + /** * Create a file. * @param path The path to the containing directory. @@ -2881,7 +2885,12 @@ public class AVMRepository Map properties = new HashMap(5); for (Map.Entry entry : props.entrySet()) { - properties.put(entry.getKey(), entry.getValue().getValue(entry.getKey())); + PropertyDefinition def = fDictionaryService.getProperty(entry.getKey()); + if (def == null) + { + continue; + } + properties.put(entry.getKey(), entry.getValue().getValue(def.getDataType().getName())); } context.put(PermissionService.PROPERTIES, properties); // TODO put node type in there to. diff --git a/source/java/org/alfresco/repo/avm/wf/AVMDeployHandler.java b/source/java/org/alfresco/repo/avm/wf/AVMDeployHandler.java new file mode 100644 index 0000000000..10c2c986fc --- /dev/null +++ b/source/java/org/alfresco/repo/avm/wf/AVMDeployHandler.java @@ -0,0 +1,270 @@ +/* + * 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.wf; + +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.Properties; + +import org.alfresco.config.JNDIConstants; +import org.alfresco.model.ContentModel; +import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.avm.actions.AVMDeployWebsiteAction; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.workflow.jbpm.JBPMNode; +import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler; +import org.alfresco.sandbox.SandboxConstants; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.avm.AVMService; +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.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.alfresco.util.ISO9075; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jbpm.graph.exe.ExecutionContext; +import org.springframework.beans.factory.BeanFactory; + + +/** + * Deploys the latest snapshot of the staging area the submission was for. + * + * @author Gavin Cornwell + */ +public class AVMDeployHandler extends JBPMSpringActionHandler +{ + private AVMService avmService; + private ActionService actionService; + private SearchService searchService; + private NodeService unprotectedNodeService; + private PermissionService unprotectedPermissionService; + private ImporterBootstrap importerBootstrap; + + private static final String BEAN_AVM_SERVICE = "AVMService"; + private static final String BEAN_ACTION_SERVICE = "actionService"; + private static final String BEAN_NODE_SERVICE = "nodeService"; + private static final String BEAN_SEARCH_SERVICE = "searchService"; + private static final String BEAN_PERMISSION_SERVICE = "permissionService"; + private static final String BEAN_IMPORTER_BOOTSTRAP = "spacesBootstrap"; + private static final String PROP_ROOT_FOLDER = "spaces.company_home.childname"; + private static final String PROP_WCM_FOLDER = "spaces.wcm.childname"; + + private static final long serialVersionUID = 5590265401983087178L; + private static final Log logger = LogFactory.getLog(AVMDeployHandler.class); + + /** + * Initialize service references. + * @param factory The BeanFactory to get references from. + */ + @Override + protected void initialiseHandler(BeanFactory factory) + { + this.avmService = (AVMService)factory.getBean(BEAN_AVM_SERVICE); + this.actionService = (ActionService)factory.getBean(BEAN_ACTION_SERVICE); + this.searchService = (SearchService)factory.getBean(BEAN_SEARCH_SERVICE); + this.importerBootstrap = (ImporterBootstrap)factory.getBean(BEAN_IMPORTER_BOOTSTRAP); + this.unprotectedNodeService = (NodeService)factory.getBean(BEAN_NODE_SERVICE); + this.unprotectedPermissionService = (PermissionService)factory.getBean(BEAN_PERMISSION_SERVICE); + } + + /** + * Do the actual work. + * @param executionContext The context to get stuff from. + */ + public void execute(ExecutionContext executionContext) throws Exception + { + // determine if the auto deploy needs to be executed + Boolean autoDeploy = (Boolean)executionContext.getContextInstance().getVariable("wcmwf_autoDeploy"); + + if (logger.isDebugEnabled()) + { + String label = (String)executionContext.getContextInstance().getVariable("wcmwf_label"); + long workflowId = executionContext.getProcessInstance().getId(); + + logger.debug("autoDeploy state for submission (workflowid: jbpm$" + workflowId + + ", label: " + label + ") is: " + autoDeploy); + } + + if (autoDeploy != null && autoDeploy.booleanValue()) + { + // get the web project node for the submission + JBPMNode webProjNode = (JBPMNode)executionContext.getContextInstance().getVariable("wcmwf_webproject"); + NodeRef webProjectRef = webProjNode.getNodeRef(); + + // get the list of live servers for the project that have the auto deploy flag turned on + List servers = findDeployToServers(webProjectRef); + + // if there are servers do the deploy + if (servers.size() > 0) + { + // Get the staging store name + NodeRef pkg = ((JBPMNode)executionContext.getContextInstance().getVariable("bpm_package")).getNodeRef(); + Pair pkgPath = AVMNodeConverter.ToAVMVersionPath(pkg); + String [] workflowStorePath = pkgPath.getSecond().split(":"); + String workflowStoreName = workflowStorePath[0]; + PropertyValue propVal = this.avmService.getStoreProperty(workflowStoreName, + SandboxConstants.PROP_WEBSITE_NAME); + String store = propVal.getStringValue(); + + if (logger.isDebugEnabled()) + logger.debug("Attempting auto deploy to store: " + store); + + // retrieve the latest snapshot number for the store + int snapshotVersionToDeploy = this.avmService.getLatestSnapshotID(store); + + // work out the path of the store that needs deploying + String pathToDeploy = store + ":/" + JNDIConstants.DIR_DEFAULT_WWW + + '/' + JNDIConstants.DIR_DEFAULT_APPBASE; + NodeRef websiteRef = AVMNodeConverter.ToNodeRef(snapshotVersionToDeploy, pathToDeploy); + + // create a deploymentattempt node to represent this deployment + String attemptId = GUID.generate(); + Map props = new HashMap(8, 1.0f); + props.put(WCMAppModel.PROP_DEPLOYATTEMPTID, attemptId); + props.put(WCMAppModel.PROP_DEPLOYATTEMPTTYPE, WCMAppModel.CONSTRAINT_LIVESERVER); + props.put(WCMAppModel.PROP_DEPLOYATTEMPTSTORE, store); + props.put(WCMAppModel.PROP_DEPLOYATTEMPTVERSION, snapshotVersionToDeploy); + props.put(WCMAppModel.PROP_DEPLOYATTEMPTTIME, new Date()); + NodeRef attempt = unprotectedNodeService.createNode(webProjectRef, + WCMAppModel.ASSOC_DEPLOYMENTATTEMPT, WCMAppModel.ASSOC_DEPLOYMENTATTEMPT, + WCMAppModel.TYPE_DEPLOYMENTATTEMPT, props).getChildRef(); + + // allow anyone to add child nodes to the deploymentattempt node + unprotectedPermissionService.setPermission(attempt, PermissionService.ALL_AUTHORITIES, + PermissionService.ADD_CHILDREN, true); + + // iterate round each server and fire off a deplyoment action + List selectedDeployToNames = new ArrayList(); + for (NodeRef serverRef: servers) + { + if (unprotectedNodeService.exists(serverRef)) + { + // get all properties of the target server + Map serverProps = unprotectedNodeService.getProperties(serverRef); + + String serverUri = AVMDeployWebsiteAction.calculateServerUri(serverProps); + String serverName = (String)serverProps.get(WCMAppModel.PROP_DEPLOYSERVERNAME); + if (serverName == null || serverName.length() == 0) + { + serverName = serverUri; + } + + // remember the servers deployed to + selectedDeployToNames.add(serverName); + + if (logger.isDebugEnabled()) + logger.debug("Auto deploying '" + websiteRef.toString() + "' to server: " + serverName); + + // create and execute the action asynchronously + Map args = new HashMap(1, 1.0f); + args.put(AVMDeployWebsiteAction.PARAM_WEBPROJECT, webProjectRef); + args.put(AVMDeployWebsiteAction.PARAM_SERVER, serverRef); + args.put(AVMDeployWebsiteAction.PARAM_ATTEMPT, attempt); + Action action = this.actionService.createAction(AVMDeployWebsiteAction.NAME, args); + this.actionService.executeAction(action, websiteRef, false, true); + } + } + + // now we know the list of selected servers set the property on the attempt node + unprotectedNodeService.setProperty(attempt, WCMAppModel.PROP_DEPLOYATTEMPTSERVERS, + (Serializable)selectedDeployToNames); + + // set the deploymentattempid property on the store this deployment was for + this.avmService.deleteStoreProperty(store, SandboxConstants.PROP_LAST_DEPLOYMENT_ID); + this.avmService.setStoreProperty(store, SandboxConstants.PROP_LAST_DEPLOYMENT_ID, + new PropertyValue(DataTypeDefinition.TEXT, attemptId)); + } + } + } + + private List findDeployToServers(NodeRef webProjectRef) + { + // get folder names + Properties configuration = this.importerBootstrap.getConfiguration(); + String rootFolder = configuration.getProperty(PROP_ROOT_FOLDER); + String wcmFolder = configuration.getProperty(PROP_WCM_FOLDER); + + // get web project name + String webProjectName = (String)this.unprotectedNodeService.getProperty( + webProjectRef, ContentModel.PROP_NAME); + String safeProjectName = ISO9075.encode(webProjectName); + + // build the query + StringBuilder query = new StringBuilder("PATH:\"/"); + query.append(rootFolder); + query.append("/"); + query.append(wcmFolder); + query.append("/cm:"); + query.append(safeProjectName); + query.append("/*\" AND @"); + query.append(NamespaceService.WCMAPP_MODEL_PREFIX); + query.append("\\:"); + query.append(WCMAppModel.PROP_DEPLOYSERVERTYPE.getLocalName()); + query.append(":\""); + query.append(WCMAppModel.CONSTRAINT_LIVESERVER); + query.append("\" AND @"); + query.append(NamespaceService.WCMAPP_MODEL_PREFIX); + query.append("\\:"); + query.append(WCMAppModel.PROP_DEPLOYONAPPROVAL.getLocalName()); + query.append(":\"true\""); + + // execute the query + ResultSet results = null; + List servers = new ArrayList(); + try + { + results = searchService.query(webProjectRef.getStoreRef(), + SearchService.LANGUAGE_LUCENE, query.toString()); + + for (NodeRef server : results.getNodeRefs()) + { + servers.add(server); + } + } + finally + { + if (results != null) + { + results.close(); + } + } + + return servers; + } +} diff --git a/source/java/org/alfresco/repo/avm/wf/AVMReleaseTestServerHandler.java b/source/java/org/alfresco/repo/avm/wf/AVMReleaseTestServerHandler.java new file mode 100644 index 0000000000..a1fa001f1f --- /dev/null +++ b/source/java/org/alfresco/repo/avm/wf/AVMReleaseTestServerHandler.java @@ -0,0 +1,147 @@ +/* + * 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.wf; + +import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.workflow.jbpm.JBPMNode; +import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jbpm.graph.exe.ExecutionContext; +import org.springframework.beans.factory.BeanFactory; + +/** + * Releases the test deploy server used by the workflow (if any). + * + * @author Gavin Cornwell + */ +public class AVMReleaseTestServerHandler extends JBPMSpringActionHandler +{ + private SearchService searchService; + private NodeService unprotectedNodeService; + + private static final String BEAN_NODE_SERVICE = "nodeService"; + private static final String BEAN_SEARCH_SERVICE = "searchService"; + + private static final long serialVersionUID = -202652488887586866L; + private static final Log logger = LogFactory.getLog(AVMReleaseTestServerHandler.class); + + /** + * Initialize service references. + * @param factory The BeanFactory to get references from. + */ + @Override + protected void initialiseHandler(BeanFactory factory) + { + this.searchService = (SearchService)factory.getBean(BEAN_SEARCH_SERVICE); + this.unprotectedNodeService = (NodeService)factory.getBean(BEAN_NODE_SERVICE); + } + + /** + * Do the actual work. + * @param executionContext The context to get stuff from. + */ + public void execute(ExecutionContext executionContext) throws Exception + { + // get the store name + NodeRef pkg = ((JBPMNode)executionContext.getContextInstance().getVariable("bpm_package")).getNodeRef(); + Pair pkgPath = AVMNodeConverter.ToAVMVersionPath(pkg); + String [] workflowStorePath = pkgPath.getSecond().split(":"); + String workflowStoreName = workflowStorePath[0]; + + // get the web project node for the submission + JBPMNode webProjNode = (JBPMNode)executionContext.getContextInstance().getVariable("wcmwf_webproject"); + NodeRef webProjectRef = webProjNode.getNodeRef(); + + if (logger.isDebugEnabled()) + logger.debug("Looking for test server to release for store: " + workflowStoreName); + + // query for the allocated test server (if one) + NodeRef testServer = findAllocatedServer(webProjectRef, workflowStoreName); + + if (testServer != null) + { + // reset the allocatedto property on the test server node + this.unprotectedNodeService.setProperty(testServer, WCMAppModel.PROP_DEPLOYSERVERALLOCATEDTO, null); + + if (logger.isDebugEnabled()) + logger.debug("Released test server '" + testServer + "' from store: " + workflowStoreName); + } + else if (logger.isDebugEnabled()) + { + logger.debug("Store '" + workflowStoreName + "' didn't have an allocated test server to release"); + } + } + + private NodeRef findAllocatedServer(NodeRef webProjectRef, String store) + { + StringBuilder query = new StringBuilder("@"); + query.append(NamespaceService.WCMAPP_MODEL_PREFIX); + query.append("\\:"); + query.append(WCMAppModel.PROP_DEPLOYSERVERALLOCATEDTO.getLocalName()); + query.append(":\""); + query.append(store); + query.append("\""); + + ResultSet results = null; + NodeRef testServer = null; + try + { + // execute the query + results = this.searchService.query(webProjectRef.getStoreRef(), + SearchService.LANGUAGE_LUCENE, query.toString()); + + if (results.length() == 1) + { + testServer = results.getNodeRef(0); + } + else if (results.length() > 1) + { + // get the first one and warn that we found many! + testServer = results.getNodeRef(0); + + if (logger.isWarnEnabled()) + logger.warn("More than one allocated test server for store '" + + store + "' was found, should only be one, first one found returned!"); + } + } + finally + { + if (results != null) + { + results.close(); + } + } + + return testServer; + } +}