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);
}