diff --git a/config/alfresco/attributes-service-context.xml b/config/alfresco/attributes-service-context.xml index d9dbb26acb..06d9b74e7d 100644 --- a/config/alfresco/attributes-service-context.xml +++ b/config/alfresco/attributes-service-context.xml @@ -18,13 +18,18 @@ - - + + - + + + + + + diff --git a/source/java/org/alfresco/repo/attributes/PropTablesCleaner.java b/source/java/org/alfresco/repo/attributes/PropTablesCleaner.java new file mode 100644 index 0000000000..5fb07672df --- /dev/null +++ b/source/java/org/alfresco/repo/attributes/PropTablesCleaner.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.attributes; + +import org.alfresco.repo.domain.propval.PropertyValueDAO; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Cleaner of unused values from the alf_prop_xxx tables. + * + * @author alex.mukha + */ +public class PropTablesCleaner +{ + private PropertyValueDAO propertyValueDAO; + private JobLockService jobLockService; + + /* 1 minute */ + private static final long LOCK_TTL = 60000L; + private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, PropTablesCleaner.class.getName()); + private static ThreadLocal> lockThreadLocal = new ThreadLocal>(); + + private static Log logger = LogFactory.getLog(PropTablesCleaner.class); + + public void checkProperties() + { + PropertyCheck.mandatory(this, "jobLockService", jobLockService); + PropertyCheck.mandatory(this, "propertyValueDAO", propertyValueDAO); + } + + public void setPropertyValueDAO(PropertyValueDAO propertyValueDAO) + { + this.propertyValueDAO = propertyValueDAO; + } + + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + + /** + * Lazily update the job lock + */ + private void refreshLock() + { + Pair lockPair = lockThreadLocal.get(); + if (lockPair == null) + { + String lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL); + Long lastLock = new Long(System.currentTimeMillis()); + // We have not locked before + lockPair = new Pair(lastLock, lockToken); + lockThreadLocal.set(lockPair); + } + else + { + long now = System.currentTimeMillis(); + long lastLock = lockPair.getFirst().longValue(); + String lockToken = lockPair.getSecond(); + // Only refresh the lock if we are past a threshold + if (now - lastLock > (long)(LOCK_TTL/2L)) + { + jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL); + lastLock = System.currentTimeMillis(); + lockPair = new Pair(lastLock, lockToken); + lockThreadLocal.set(lockPair); + } + } + } + + /** + * Release the lock after the job completes + */ + private void releaseLock() + { + Pair lockPair = lockThreadLocal.get(); + if (lockPair != null) + { + // We can't release without a token + try + { + jobLockService.releaseLock(lockPair.getSecond(), LOCK_QNAME); + } + finally + { + // Reset + lockThreadLocal.set(null); + } + } + // else: We can't release without a token + } + + public void execute() + { + checkProperties(); + try + { + refreshLock(); + propertyValueDAO.cleanupUnusedValues(); + } + catch (LockAcquisitionException e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Skipping prop tables cleaning. " + e.getMessage()); + } + } + finally + { + releaseLock(); + } + } +} diff --git a/source/java/org/alfresco/repo/attributes/PropTablesCleanupJob.java b/source/java/org/alfresco/repo/attributes/PropTablesCleanupJob.java index ebe3323fb7..aabd672c39 100644 --- a/source/java/org/alfresco/repo/attributes/PropTablesCleanupJob.java +++ b/source/java/org/alfresco/repo/attributes/PropTablesCleanupJob.java @@ -18,7 +18,10 @@ */ package org.alfresco.repo.attributes; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.domain.propval.PropertyValueDAO; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; @@ -31,19 +34,19 @@ import org.quartz.JobExecutionException; */ public class PropTablesCleanupJob implements Job { - protected static final Object PROPERTY_VALUE_DAO_KEY = "propertyValueDAO"; - @Override public void execute(JobExecutionContext jobCtx) throws JobExecutionException { JobDataMap jobData = jobCtx.getJobDetail().getJobDataMap(); - - PropertyValueDAO propertyValueDAO = (PropertyValueDAO) jobData.get(PROPERTY_VALUE_DAO_KEY); - if (propertyValueDAO == null) + // extract the feed cleaner to use + Object cleanerObj = jobData.get("propTablesCleaner"); + + if (cleanerObj == null || !(cleanerObj instanceof PropTablesCleaner)) { - throw new IllegalArgumentException(PROPERTY_VALUE_DAO_KEY + " in job data map was null"); + throw new AlfrescoRuntimeException( + "PropTablesCleanupJob data must contain valid 'PropTablesCleaner' reference"); } - - propertyValueDAO.cleanupUnusedValues(); + PropTablesCleaner cleaner = (PropTablesCleaner) cleanerObj; + cleaner.execute(); } } diff --git a/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobIntegrationTest.java b/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobIntegrationTest.java index 882eb9623a..9357059d5c 100644 --- a/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobIntegrationTest.java +++ b/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobIntegrationTest.java @@ -21,7 +21,6 @@ package org.alfresco.repo.attributes; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import org.alfresco.repo.domain.propval.PropertyValueDAO; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.CronTriggerBean; import org.junit.Before; @@ -57,7 +56,7 @@ public class PropTablesCleanupJobIntegrationTest { JobDetail jobDetail = jobTrigger.getJobDetail(); assertEquals(PropTablesCleanupJob.class, jobDetail.getJobClass()); - assertTrue("JobDetail did not contain PropertyValueDAO reference", - jobDetail.getJobDataMap().get("propertyValueDAO") instanceof PropertyValueDAO); + assertTrue("JobDetail did not contain PropTablesCleaner reference", + jobDetail.getJobDataMap().get("propTablesCleaner") instanceof PropTablesCleaner); } } diff --git a/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobTest.java b/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobTest.java index 7ec2aa1671..e85e7a9780 100644 --- a/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobTest.java +++ b/source/test-java/org/alfresco/repo/attributes/PropTablesCleanupJobTest.java @@ -1,10 +1,9 @@ package org.alfresco.repo.attributes; -import static org.junit.Assert.*; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import org.alfresco.repo.domain.propval.PropertyValueDAO; +import org.alfresco.error.AlfrescoRuntimeException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -25,14 +24,14 @@ public class PropTablesCleanupJobTest { private PropTablesCleanupJob cleanupJob; private @Mock JobExecutionContext jobCtx; - private @Mock PropertyValueDAO propValueDAO; + private @Mock PropTablesCleaner propTablesCleaner; private JobDetail jobDetail; @Before public void setUp() throws Exception { jobDetail = new JobDetail("propTablesCleanupJob", PropTablesCleanupJob.class); - jobDetail.getJobDataMap().put(PropTablesCleanupJob.PROPERTY_VALUE_DAO_KEY, propValueDAO); + jobDetail.getJobDataMap().put("propTablesCleaner", propTablesCleaner); cleanupJob = new PropTablesCleanupJob(); when(jobCtx.getJobDetail()).thenReturn(jobDetail); @@ -43,20 +42,20 @@ public class PropTablesCleanupJobTest { cleanupJob.execute(jobCtx); - verify(propValueDAO).cleanupUnusedValues(); + verify(propTablesCleaner).execute(); } - @Test(expected=IllegalArgumentException.class) - public void testMissingPropertyValueDAO() throws JobExecutionException + @Test(expected=AlfrescoRuntimeException.class) + public void testMissingPropTablesCleaner() throws JobExecutionException { - jobDetail.getJobDataMap().put(PropTablesCleanupJob.PROPERTY_VALUE_DAO_KEY, null); + jobDetail.getJobDataMap().put("propTablesCleaner", null); cleanupJob.execute(jobCtx); } - @Test(expected=ClassCastException.class) - public void testWrongTypeForPropertyValueDAO() throws JobExecutionException + @Test(expected=AlfrescoRuntimeException.class) + public void testWrongTypeForPropTablesCleaner() throws JobExecutionException { - jobDetail.getJobDataMap().put(PropTablesCleanupJob.PROPERTY_VALUE_DAO_KEY, "This is not a PropertyValueDAO"); + jobDetail.getJobDataMap().put("propTablesCleaner", "This is not a PropTablesCleaner"); cleanupJob.execute(jobCtx); }