diff --git a/config/alfresco/extension/deployment-attempt-cleaner-context.xml.sample b/config/alfresco/extension/deployment-attempt-cleaner-context.xml.sample new file mode 100644 index 0000000000..9d0788eee0 --- /dev/null +++ b/config/alfresco/extension/deployment-attempt-cleaner-context.xml.sample @@ -0,0 +1,57 @@ + + + + + + + + + + admin + + + + 180 + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.avm.AVMDeploymentAttemptCleanerJob + + + + + + + + + + + + + + + + 0 0 4 * * ? + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleaner.java b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleaner.java new file mode 100644 index 0000000000..1224d0e0c8 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleaner.java @@ -0,0 +1,178 @@ +/* + * 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.util.Calendar; +import java.util.Date; + +import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +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.service.transaction.TransactionService; +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 AVMDeploymentAttemptCleaner +{ + // defaults in case these properties are not configured in Spring + protected String adminUserName = "admin"; + protected long maxAge = 180L; + + protected NodeService nodeService; + protected TransactionService transactionService; + protected SearchService searchService; + protected ImporterBootstrap importerBootstrap; + + private static Log logger = LogFactory.getLog(AVMDeploymentAttemptCleaner.class); + + public AVMDeploymentAttemptCleaner() + { + } + + public void setAdminUserName(String adminUserName) + { + this.adminUserName = adminUserName; + } + + public void setMaxAge(long maxAge) + { + this.maxAge = new Long(maxAge); + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setImporterBootstrap(ImporterBootstrap importerBootstrap) + { + this.importerBootstrap = importerBootstrap; + } + + /** + * 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 + { + RetryingTransactionCallback expiredContentWork = new RetryingTransactionCallback() + { + public String execute() throws Exception + { + cleanAttempts(); + return null; + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(expiredContentWork); + } + }; + + // perform the work as the system user + AuthenticationUtil.runAs(authorisedWork, this.adminUserName); + } + + /** + * Entry point. + */ + private void cleanAttempts() + { + // calculate the date 'maxAge' days before today + long daysInMs = 1000L*60L*60L*24L*this.maxAge; + Date toDate = new Date(new Date().getTime() - daysInMs); + + // build the query to find deployment attempts older than this.maxAge + Calendar cal = Calendar.getInstance(); + cal.setTime(toDate); + StringBuilder query = new StringBuilder("@"); + query.append(NamespaceService.WCMAPP_MODEL_PREFIX); + query.append("\\:"); + query.append(WCMAppModel.PROP_DEPLOYATTEMPTTIME.getLocalName()); + query.append(":[0001\\-01\\-01T00:00:00 TO "); + query.append(cal.get(Calendar.YEAR)); + query.append("\\-"); + query.append((cal.get(Calendar.MONTH)+1)); + query.append("\\-"); + query.append(cal.get(Calendar.DAY_OF_MONTH)); + query.append("T00:00:00]"); + + if (logger.isDebugEnabled()) + logger.debug("Finding old deploymentattempt nodes using query: " + query.toString()); + + // do the query + ResultSet results = null; + try + { + // execute the query + results = searchService.query(this.importerBootstrap.getStoreRef(), + SearchService.LANGUAGE_LUCENE, query.toString()); + + if (logger.isDebugEnabled()) + logger.debug("Deleting " + results.length() + " old deployment attempts"); + + // iterate through the attempt nodes and delete them + for (NodeRef attempt : results.getNodeRefs()) + { + this.nodeService.deleteNode(attempt); + + if (logger.isDebugEnabled()) + logger.debug("Deleted deployment attempt: " + attempt); + } + } + finally + { + if (results != null) + { + results.close(); + } + } + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerJob.java b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerJob.java new file mode 100644 index 0000000000..e868b33169 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerJob.java @@ -0,0 +1,62 @@ +/* + * 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 deployment attempt cleaner. + * + *

+ * The following parameters are required: + *

    + *
  • deploymentAttemptCleaner: The deployment attempt cleaner instance
  • + *
+ * + * @author gavinc + */ +public class AVMDeploymentAttemptCleanerJob implements Job +{ + /** + * Searches for old deployment attempts and removes them. + * + * @param context The job context + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + // get the expired content processor bean from the job context + AVMDeploymentAttemptCleaner cleaner = + (AVMDeploymentAttemptCleaner)context.getJobDetail().getJobDataMap().get("deploymentAttemptCleaner"); + if (cleaner == null) + { + throw new JobExecutionException("Missing job data: deploymentAttemptCleaner"); + } + + // execute the cleaner to do the actual work + cleaner.execute(); + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerTest.java b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerTest.java new file mode 100644 index 0000000000..c903352260 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerTest.java @@ -0,0 +1,46 @@ +package org.alfresco.repo.avm; + + +import junit.framework.TestCase; + +import org.alfresco.repo.avm.AVMDeploymentAttemptCleaner; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @see org.alfresco.repo.avm.AVMDeploymentAttemptCleaner + * + * @author gavinc + */ +public class AVMDeploymentAttemptCleanerTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private AVMDeploymentAttemptCleaner cleaner; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); + ImporterBootstrap importerBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); + NodeService nodeService = serviceRegistry.getNodeService(); + SearchService searchService = serviceRegistry.getSearchService(); + TransactionService transactionService = serviceRegistry.getTransactionService(); + this.cleaner = new AVMDeploymentAttemptCleaner(); + this.cleaner.setNodeService(nodeService); + this.cleaner.setSearchService(searchService); + this.cleaner.setTransactionService(transactionService); + this.cleaner.setImporterBootstrap(importerBootstrap); +// this.cleaner.setMaxAge(30); + } + + public void testProcessor() throws Exception + { + this.cleaner.execute(); + } +} diff --git a/source/java/org/alfresco/repo/avm/actions/AVMDeployWebsiteAction.java b/source/java/org/alfresco/repo/avm/actions/AVMDeployWebsiteAction.java index dc060de470..deecb8605e 100644 --- a/source/java/org/alfresco/repo/avm/actions/AVMDeployWebsiteAction.java +++ b/source/java/org/alfresco/repo/avm/actions/AVMDeployWebsiteAction.java @@ -28,7 +28,6 @@ package org.alfresco.repo.avm.actions; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -80,6 +79,7 @@ public class AVMDeployWebsiteAction extends ActionExecuterAbstractBase private String defaultRemoteUsername = "admin"; private String defaultRemotePassword = "admin"; private String defaultTargetName = "default"; + private List configuredCallbacks; private DeploymentService deployService; private ContentService contentService; private NodeService nodeService; @@ -176,6 +176,11 @@ public class AVMDeployWebsiteAction extends ActionExecuterAbstractBase { this.defaultTargetName = defaultTargetName; } + + public void setCallbacks(List callbacks) + { + this.configuredCallbacks = callbacks; + } /** * @param service The NodeService instance @@ -335,6 +340,20 @@ public class AVMDeployWebsiteAction extends ActionExecuterAbstractBase patterns.add(excludes); regexMatcher.setPatterns(patterns); } + + // create a list of all the callback objects + List callbacks = new ArrayList(); + if (callback != null) + { + // if present add the callback passed as a parameter (usually for UI purposes) + callbacks.add(callback); + } + if (this.configuredCallbacks != null && this.configuredCallbacks.size() > 0) + { + // add the configured callbacks + callbacks.addAll(this.configuredCallbacks); + } + // take a note of the current date/time Date startDate = new Date(); @@ -362,13 +381,6 @@ public class AVMDeployWebsiteAction extends ActionExecuterAbstractBase logger.debug("Performing file server deployment to " + host + ":" + port + " using deploymentserver: " + serverProps); - // TODO: Added new NameMatcher parameter to deploy methods. It acts as a filter. - // Any matching path names are ignored for deployment purposes. - List callbacks = new ArrayList(); - if (callback != null) - { - callbacks.add(callback); - } report = this.deployService.deployDifferenceFS(version, path, host, port, remoteUsername, remotePassword, targetName, regexMatcher, true, false, false, callbacks); } @@ -378,13 +390,6 @@ public class AVMDeployWebsiteAction extends ActionExecuterAbstractBase logger.debug("Performing Alfresco deployment to " + host + ":" + port + " using deploymentserver: " + serverProps); - // TODO: Added new NameMatcher parameter to deploy methods. It acts as a filter. - // Any matching path names are ignored for deployment purposes. - List callbacks = new ArrayList(); - if (callback != null) - { - callbacks.add(callback); - } report = this.deployService.deployDifference(version, path, host, port, remoteUsername, remotePassword, targetPath, regexMatcher, true, false, false, callbacks); } @@ -393,25 +398,6 @@ public class AVMDeployWebsiteAction extends ActionExecuterAbstractBase { deployError = err; logger.error(deployError); - - // report the error to the callback object - // TODO: See if this can be incorporated into the DeploymentCallback I/F - if (callback != null) - { - // to avoid a circular dependency use reflection to call the method - try - { - Method method = callback.getClass().getMethod("errorOccurred", new Class[] {Throwable.class}); - if (method != null) - { - method.invoke(callback, new Object[] {err}); - } - } - catch (Throwable e) - { - logger.warn("Failed to inform deployment monitor of deployment failure", e); - } - } } if (report != null) @@ -472,8 +458,16 @@ public class AVMDeployWebsiteAction extends ActionExecuterAbstractBase reportProps.put(WCMAppModel.PROP_DEPLOYSUCCESSFUL, (report != null)); if (report == null && error != null) { - // add error message as fail reason if appropriate - reportProps.put(WCMAppModel.PROP_DEPLOYFAILEDREASON, error.getMessage()); + // add error message as fail reason if appropriate (the reported + // exception is a wrapper so get the detail from the cause) + String errorMsg = error.getMessage(); + Throwable cause = error.getCause(); + if (cause != null) + { + errorMsg = cause.getMessage(); + } + + reportProps.put(WCMAppModel.PROP_DEPLOYFAILEDREASON, errorMsg); } reportRef = this.nodeService.createNode(attempt, WCMAppModel.ASSOC_DEPLOYMENTREPORTS, WCMAppModel.ASSOC_DEPLOYMENTREPORTS, diff --git a/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java b/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java index 1dce1d1c9e..1d8bbad6ab 100644 --- a/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java +++ b/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java @@ -30,6 +30,7 @@ import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -137,9 +138,12 @@ public class WorkingCopyAspect // Get the origional node NodeRef origNodeRef = (NodeRef)this.nodeService.getProperty(nodeRef, ContentModel.PROP_COPY_REFERENCE); if (origNodeRef != null) - { - // Release the lock on the origional node - this.lockService.unlock(origNodeRef); + { + if (this.lockService.getLockStatus(origNodeRef).equals(LockStatus.NO_LOCK) == false) + { + // Release the lock on the origional node + this.lockService.unlock(origNodeRef); + } } } } diff --git a/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java b/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java index fc53aefdc3..082458c380 100644 --- a/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java +++ b/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java @@ -282,7 +282,7 @@ public class DeploymentServiceImpl implements DeploymentService { DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.FAILED, new Pair(version, srcPath), - dstPath); + dstPath, e.getMessage()); for (DeploymentCallback callback : callbacks) { callback.eventOccurred(event); @@ -310,7 +310,7 @@ public class DeploymentServiceImpl implements DeploymentService { DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.FAILED, new Pair(version, srcPath), - dstPath); + dstPath, e.getMessage()); for (DeploymentCallback callback : callbacks) { callback.eventOccurred(event); @@ -799,10 +799,11 @@ public class DeploymentServiceImpl implements DeploymentService List callbacks) { DeploymentReport report = new DeploymentReport(); - DeploymentReceiverService service = getReceiver(hostName, port); + DeploymentReceiverService service = null; String ticket = null; try { + service = getReceiver(hostName, port); DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.START, new Pair(version, srcPath), target); @@ -840,12 +841,17 @@ public class DeploymentServiceImpl implements DeploymentService { DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.FAILED, new Pair(version, srcPath), - target); + target, e.getMessage()); for (DeploymentCallback callback : callbacks) { callback.eventOccurred(event); } - service.abort(ticket); + + if (service != null) + { + service.abort(ticket); + } + throw new AVMException("Deployment to: " + target + " failed.", e); } } diff --git a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java index 3423464a77..d9ca8b0f78 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java @@ -50,6 +50,7 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se public TransactionImpl() { + this.commitTimeMs = Long.valueOf(0); } @Override diff --git a/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentEvent.java b/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentEvent.java index 77ae409852..09997884b3 100644 --- a/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentEvent.java +++ b/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentEvent.java @@ -56,6 +56,8 @@ public class DeploymentEvent implements Serializable private Pair fSource; private String fDestination; + + private String fMessage; public DeploymentEvent(Type type, Pair source, String destination) { @@ -63,6 +65,13 @@ public class DeploymentEvent implements Serializable fSource = source; fDestination = destination; } + + public DeploymentEvent(Type type, Pair source, String destination, String message) + { + this(type, source, destination); + + fMessage = message; + } /** * Get the type of the event. @@ -90,12 +99,28 @@ public class DeploymentEvent implements Serializable { return fDestination; } + + /** + * Get the message. + * @return + */ + public String getMessage() + { + return fMessage; + } /** * Get a String representation. */ public String toString() { - return fType + ": " + fSource + " -> " + fDestination; + String str = fType + ": " + fSource + " -> " + fDestination; + + if (fMessage != null) + { + str = str + " (" + fMessage + ")"; + } + + return str; } }