Merged HEAD-BUG-FIX (5.0/Cloud) to HEAD (4.3/Cloud)

71594: Merged V4.2-BUG-FIX (4.2.3) to HEAD-BUG-FIX (4.3/Cloud)
      70332: Merged V4.1-BUG-FIX (4.1.9) to V4.2-BUG-FIX (4.2.3)
         70292: Merged DEV to V4.1-BUG-FIX (4.1.9)
            60077: Created branch for MNT-10067 work.
            60101: MNT-10067: first commit of a generic SQL script runner.
            Very rough and ready extraction of SchemaBootstrap SQL script execution functionality. Will serve as the basis for the implementation of MNT-10067.
            60147: MNT-10067: added tests for "dialect" placeholder resolution, including overriding of dialects.
            60180: MNT-10067: exception thrown when unable to find SQL script to execute
            60187: MNT-10067: renamed ScriptExecutor to ScriptExecutorImpl to make way for an interface definition.
            60188: MNT-10067: introduced a ScriptExecutor interface.
            60189: MNT-10067: renamed ScriptExecutorTest
            60190: MNT-10067: added ScriptExecutorImplIntegrationTest to repo test suite.
            60194: MNT-10067: a very simple initial implementation of a SQL script runner capable of running multiple scripts in a given
            directory.
            60195: MNT-10067: added integration test for ScriptBundleExecutorImpl.
            60196: MNT-10067: moved ScriptBundleExecutorImplTest to correct source tree.
            60197: MNT-10067: added ScriptBundleExecutorImplIntegrationTest to repo test suite.
            60263: MNT-10067: ScriptBundleExecutor(Impl) now stops executing the main batch of scripts upon failure and runs a post-script.
            60459: MNT-10067: minor change to test data to avoid implying that ScriptBundleExecutor.exec(String, String...) has an always-run
            final script.
            60482: MNT-10067: added integration test for ScriptBundleExecutor.execWithPostScript()
            60483: MNT-10067: committed missing files from r60482
            60488: MNT-10067: set appropriate log levels for log4j
            60620: MNT-10067: added alf_props_xxx clean-up script.
            60623: MNT-10067: minor tidy up of ScriptExecutorImpl (tidy imports, group fields at top of class)
            60625: MNT-10067: further tidy up ScriptExecutorImpl (removed redundant constants, made externally unused public constant private)
            60629: MNT-10067: fix tests broken by introduction of scriptExecutor bean in production code.
            60662: MNT-10067: added tests to check deletion of doubles, serializables and dates.
            61378: MNT-10067 : Cleanup alf_prop_XXX data
            Added MySQL, Oracle DB, MS SQL Server and IBM DB2 scripts.
            63371: MNT-10067: removed the vacuum and analyze statements from the postgresql script.
            63372: MNT-10067: replaced begin and commit statements (PostgreSQL script only) with --BEGIN TXN and --END TXN, handled by the
            script executor.
            63568: MNT-10067 : Cleanup alf_prop_XXX data
            Added start and end transaction marks to the scripts.
            64115: MNT-10067: added Quartz job that by default doesn't fire until 2099 and can be manually invoked over JMX.
            64223: MNT-10067: improved testing for AuditDAOTest and added PropertyValueDAOTest
            64685: MNT-10067: added AttributeServiceTest
            65796: MNT-10067 : Cleanup alf_prop_XXX data
            Implemented a performance test.
            65983: MNT-10067 : Cleanup alf_prop_XXX data
            Reworked the MySQL script.
            Added time measurements for entry creation.
            66116: MNT-10067 : Cleanup alf_prop_XXX data
            For MySQL:
            1) Renamed temp tables.
            2) Split the script into execution and cleanup of temp tables parts.
            67023: MNT-10067 : Cleanup alf_prop_XXX data
            Modified MySQL script to skip null values from alf_prop_unique_ctx.prop1_id.
            67199: MNT-10067 : Cleanup alf_prop_XXX data
            Implemented the latest changes of the script for other DB flavors.
            Removed the MS SQL Server transaction marks.
            67763: MNT-10067 : Cleanup alf_prop_XXX data
            Removed unnecessary temporary index dropping.
            Added additional cleanup before main script execution.
            68710: MNT-10067 : Cleanup alf_prop_XXX data
            Added batch logging.
            Moved clearCaches() statement in PropertyValueDAOImpl.cleanupUnusedValues() to finally block.
            68711: MNT-10067 : Cleanup alf_prop_XXX data
            Added batching for MySQL script.
            69602: MNT-10067 : Cleanup alf_prop_XXX data
            Updated scripts for all DB flavors with batching.
            69768: MNT-10067 : Cleanup alf_prop_XXX data
            Fixed failing ScriptBundleExecutorImplIntegrationTest and ScriptExecutorImplIntegrationTest.
            70058: Sync with latest changes in V4.1-BUG-FIX.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@74691 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Will Abson
2014-06-25 15:26:31 +00:00
parent b703c1de1f
commit 04cead57a6
39 changed files with 2139 additions and 2 deletions

View File

@@ -419,6 +419,8 @@ public class Repository01TestSuite extends TestSuite
suite.addTest(new JUnit4TestAdapter(org.alfresco.util.CronTriggerBeanSystemTest.class));
suite.addTest(new JUnit4TestAdapter(org.alfresco.filesys.auth.cifs.CifsAuthenticatorKerberosTest.class));
suite.addTest(new JUnit4TestAdapter(org.alfresco.filesys.auth.cifs.CifsAuthenticatorPassthruTest.class));
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.domain.schema.script.ScriptExecutorImplIntegrationTest.class));
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.domain.schema.script.ScriptBundleExecutorImplIntegrationTest.class));
}
static void tests65(TestSuite suite)

View File

@@ -20,13 +20,18 @@ package org.alfresco.repo.attributes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import junit.framework.TestCase;
import org.alfresco.repo.domain.propval.PropValGenerator;
import org.alfresco.repo.domain.propval.PropertyValueDAO;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.Pair;
import org.apache.commons.lang.mutable.MutableInt;
import org.springframework.context.ApplicationContext;
@@ -47,12 +52,14 @@ public class AttributeServiceTest extends TestCase
private ApplicationContext ctx;
private AttributeService attributeService;
private PropertyValueDAO propertyValueDAO;
@Override
protected void setUp() throws Exception
{
ctx = ApplicationContextHelper.getApplicationContext();
attributeService = (AttributeService) ctx.getBean("AttributeService");
propertyValueDAO = (PropertyValueDAO) ctx.getBean("propertyValueDAO");
}
@Override
@@ -132,4 +139,57 @@ public class AttributeServiceTest extends TestCase
assertEquals(2, results.size());
assertEquals(2, counter.getValue());
}
public void testRemoveOrphanedProps()
{
final Serializable[] stringKey = new String[] { "z", "q", "string" };
final Serializable[] doubleKey = new String[] { "z", "q", "double" };
final Serializable[] dateKey = new String[] { "z", "q", "date" };
// Make sure there's nothing left from previous failed test runs etc.
attributeService.removeAttributes(stringKey);
attributeService.removeAttributes(doubleKey);
attributeService.removeAttributes(dateKey);
final PropValGenerator valueGen = new PropValGenerator(propertyValueDAO);
// Create some values
final String stringValue = valueGen.createUniqueString();
attributeService.createAttribute(stringValue, stringKey);
final Double doubleValue = valueGen.createUniqueDouble();
attributeService.createAttribute(doubleValue, doubleKey);
final Date dateValue = valueGen.createUniqueDate();
attributeService.createAttribute(dateValue, dateKey);
// Remove the properties, potentially leaving oprhaned prop values.
attributeService.removeAttributes(stringKey);
attributeService.removeAttributes(doubleKey);
attributeService.removeAttributes(dateKey);
// Check there are some persisted values to delete, otherwise there is no
// need to run the cleanup script in the first place.
assertEquals(stringValue, propertyValueDAO.getPropertyValue(stringValue).getSecond());
assertEquals(doubleValue, propertyValueDAO.getPropertyValue(doubleValue).getSecond());
assertEquals(dateValue, propertyValueDAO.getPropertyValue(dateValue).getSecond());
// Run the cleanup script - should remove the orphaned values.
propertyValueDAO.cleanupUnusedValues();
// Check that the cleanup script removed the orphaned values.
assertPropDeleted(propertyValueDAO.getPropertyValue(stringValue));
assertPropDeleted(propertyValueDAO.getPropertyValue(doubleValue));
assertPropDeleted(propertyValueDAO.getPropertyValue(dateValue));
}
private void assertPropDeleted(Pair<Long, ?> value)
{
if (value != null)
{
String msg = String.format("Property value [%s=%s] should have been deleted by cleanup script.",
value.getSecond().getClass().getSimpleName(), value.getSecond());
fail(msg);
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2005-2014 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.attributes;
import static org.junit.Assert.*;
import java.util.Date;
import org.alfresco.repo.domain.propval.PropertyValueDAO;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.CronTriggerBean;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.quartz.JobDetail;
import org.quartz.SchedulerException;
import org.springframework.context.ApplicationContext;
import com.ibm.icu.util.Calendar;
/**
* Integration tests for the {@link PropTablesCleanupJob} class.
*
* @author Matt Ward
*/
public class PropTablesCleanupJobIntegrationTest
{
private static ApplicationContext ctx;
private CronTriggerBean jobTrigger;
@BeforeClass
public static void setUpClass()
{
ctx = ApplicationContextHelper.getApplicationContext();
}
@Before
public void setUp() throws Exception
{
jobTrigger = ctx.getBean("propTablesCleanupTrigger", CronTriggerBean.class);
}
@Test
public void checkJobWillNeverRunByDefault() throws Exception
{
Date fireTime = jobTrigger.getTrigger().getFireTimeAfter(new Date());
Calendar calendar = Calendar.getInstance();
// Far into the future, we count this as never.
calendar.setTime(fireTime);
assertEquals(2099, calendar.get(Calendar.YEAR));
}
@Test
public void checkJobDetails()
{
JobDetail jobDetail = jobTrigger.getJobDetail();
assertEquals(PropTablesCleanupJob.class, jobDetail.getJobClass());
assertTrue("JobDetail did not contain PropertyValueDAO reference",
jobDetail.getJobDataMap().get("propertyValueDAO") instanceof PropertyValueDAO);
}
}

View File

@@ -0,0 +1,63 @@
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.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* Tests for the {@link PropTablesCleanupJob} class.
*
* @author Matt Ward
*/
@RunWith(MockitoJUnitRunner.class)
public class PropTablesCleanupJobTest
{
private PropTablesCleanupJob cleanupJob;
private @Mock JobExecutionContext jobCtx;
private @Mock PropertyValueDAO propValueDAO;
private JobDetail jobDetail;
@Before
public void setUp() throws Exception
{
jobDetail = new JobDetail("propTablesCleanupJob", PropTablesCleanupJob.class);
jobDetail.getJobDataMap().put(PropTablesCleanupJob.PROPERTY_VALUE_DAO_KEY, propValueDAO);
cleanupJob = new PropTablesCleanupJob();
when(jobCtx.getJobDetail()).thenReturn(jobDetail);
}
@Test
public void testExecute() throws JobExecutionException
{
cleanupJob.execute(jobCtx);
verify(propValueDAO).cleanupUnusedValues();
}
@Test(expected=IllegalArgumentException.class)
public void testMissingPropertyValueDAO() throws JobExecutionException
{
jobDetail.getJobDataMap().put(PropTablesCleanupJob.PROPERTY_VALUE_DAO_KEY, null);
cleanupJob.execute(jobCtx);
}
@Test(expected=ClassCastException.class)
public void testWrongTypeForPropertyValueDAO() throws JobExecutionException
{
jobDetail.getJobDataMap().put(PropTablesCleanupJob.PROPERTY_VALUE_DAO_KEY, "This is not a PropertyValueDAO");
cleanupJob.execute(jobCtx);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -23,16 +23,22 @@ import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
import org.alfresco.repo.domain.audit.AuditDAO.AuditApplicationInfo;
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
import org.alfresco.repo.domain.propval.PropValGenerator;
import org.alfresco.repo.domain.propval.PropertyValueDAO;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
@@ -62,6 +68,7 @@ public class AuditDAOTest extends TestCase
private TransactionService transactionService;
private RetryingTransactionHelper txnHelper;
private AuditDAO auditDAO;
private PropertyValueDAO propertyValueDAO;
@Override
public void setUp() throws Exception
@@ -71,6 +78,7 @@ public class AuditDAOTest extends TestCase
txnHelper = transactionService.getRetryingTransactionHelper();
auditDAO = (AuditDAO) ctx.getBean("auditDAO");
propertyValueDAO = ctx.getBean(PropertyValueDAO.class);
}
public void testAuditModel() throws Exception
@@ -527,4 +535,168 @@ public class AuditDAOTest extends TestCase
return 0;
}
}
/**
* MNT-10067: use a script to delete the orphaned audit data (property values).
*/
public void testScriptCanDeleteOrphanedProps() throws Exception
{
// single test
scriptCanDeleteOrphanedPropsWork(false);
}
private void scriptCanDeleteOrphanedPropsWork(final boolean performance) throws Exception
{
final int iterationStep, maxIterations;
if (performance)
{
iterationStep = 1000;
maxIterations = 1000;
}
else
{
iterationStep = 1;
maxIterations = 1;
}
UserTransaction txn;
for (int i = iterationStep; i <= maxIterations*iterationStep; i+=iterationStep)
{
List<String> stringValues = new LinkedList<String>();
List<Double> doubleValues = new LinkedList<Double>();
List<Date> dateValues = new LinkedList<Date>();
txn = transactionService.getUserTransaction();
long startCreate = System.currentTimeMillis();
txn.begin();
for (int j = 0; j < i; j++)
{
PropValGenerator valueGen = new PropValGenerator(propertyValueDAO);
String stringValue = valueGen.createUniqueString();
stringValues.add(stringValue);
Double doubleValue = valueGen.createUniqueDouble();
doubleValues.add(doubleValue);
Date dateValue = valueGen.createUniqueDate();
dateValues.add(dateValue);
AuditQueryCallbackImpl preDeleteCallback = new AuditQueryCallbackImpl();
AuditQueryCallbackImpl resultsCallback = new AuditQueryCallbackImpl();
AuditApplicationInfo info1 = createAuditApp();
String app1 = info1.getName();
String username = "alexi";
Map<String, Serializable> values = new HashMap<String, Serializable>();
values.put("/a/b/string-" + j, stringValue);
values.put("/a/b/double-" + j, doubleValue);
values.put("/a/b/date-" + j, dateValue);
// TODO: how to deal with Serializable values which cannot be retrieved later in test by value alone?
long now = System.currentTimeMillis();
auditDAO.createAuditEntry(info1.getId(), now, username, values);
auditDAO.findAuditEntries(preDeleteCallback, new AuditQueryParameters(), -1);
assertEquals(1, preDeleteCallback.numEntries(app1));
// Delete audit entries between times - for all applications.
auditDAO.deleteAuditEntries(info1.getId(), null, null);
if (!performance)
{
auditDAO.findAuditEntries(resultsCallback, new AuditQueryParameters(), -1);
assertEquals("All entries should have been deleted from app1", 0, resultsCallback.numEntries(app1));
}
}
txn.commit();
System.out.println("Created values for " + i + " entries in " + (System.currentTimeMillis() - startCreate) + " ms.");
txn = transactionService.getUserTransaction();
txn.begin();
if (!performance)
{
// Check there are some persisted values to delete.
// Unlike PropertyValueDAOTest we're using the getPropertyValue() method here,
// instead of the datatype-specific methods (e.g. getPropertyStringValue()).
// This is because AuditDAO persists an entire map of values resulting in different behaviour
// (i.e. dates are persisted as Serializable)
for (String stringValue : stringValues)
{
assertEquals(stringValue, propertyValueDAO.getPropertyValue(stringValue).getSecond());
}
for (Double doubleValue : doubleValues)
{
assertEquals(doubleValue, propertyValueDAO.getPropertyValue(doubleValue).getSecond());
}
for (Date dateValue : dateValues)
{
assertEquals(dateValue, propertyValueDAO.getPropertyValue(dateValue).getSecond());
}
}
long startDelete = System.currentTimeMillis();
propertyValueDAO.cleanupUnusedValues();
txn.commit();
System.out.println("Cleaned values for " + i + " entries in " + (System.currentTimeMillis() - startDelete) + " ms.");
if (!performance)
{
// Check all the properties have been deleted.
txn = transactionService.getUserTransaction();
txn.begin();
for (String stringValue : stringValues)
{
assertPropDeleted(propertyValueDAO.getPropertyValue(stringValue));
}
for (Double doubleValue : doubleValues)
{
assertPropDeleted(propertyValueDAO.getPropertyValue(doubleValue));
}
for (Date dateValue : dateValues)
{
assertPropDeleted(propertyValueDAO.getPropertyValue(dateValue));
}
txn.commit();
}
}
}
private void assertPropDeleted(Pair<Long, ?> value)
{
if (value != null)
{
String msg = String.format("Property value [%s=%s] should have been deleted by cleanup script.",
value.getSecond().getClass().getSimpleName(), value.getSecond());
fail(msg);
}
}
public void scriptCanDeleteOrphanedPropsPerformance() throws Exception
{
scriptCanDeleteOrphanedPropsWork(true);
}
public static void main(String[] args)
{
try
{
AuditDAOTest test = new AuditDAOTest();
test.setUp();
System.out.println("Press any key to run performance test.");
System.in.read();
test.scriptCanDeleteOrphanedPropsPerformance();
System.out.println("Press any key to shutdown.");
System.in.read();
test.tearDown();
}
catch (Throwable e)
{
e.printStackTrace();
}
finally
{
ApplicationContextHelper.closeApplicationContext();
}
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2005-2014 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.domain.propval;
import static org.junit.Assert.assertNotNull;
import java.io.Serializable;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import org.alfresco.util.Pair;
/**
* Creates property values suitable for use in testing.
*
* @author Matt Ward
*/
public class PropValGenerator
{
private static final Random rand = new Random();
private final PropertyValueDAO propertyValueDAO;
private final DoubleGen doubleGen = new DoubleGen();
private final DateGen dateGen = new DateGen();
private final SerializableGen serGen = new SerializableGen();
public PropValGenerator(PropertyValueDAO propertyValueDAO)
{
this.propertyValueDAO = propertyValueDAO;
}
public String createUniqueString()
{
// No need to do anything more clever than create a UUID.
return UUID.randomUUID().toString();
}
public Double createUniqueDouble()
{
return doubleGen.getUnusedValue();
}
public Date createUniqueDate()
{
return dateGen.getUnusedValue();
}
public Serializable createUniqueSerializable()
{
return serGen.getUnusedValue();
}
private class DoubleGen extends UniqueValueGenerator<Double>
{
@Override
protected Double createValue()
{
return (Math.pow(2,32)) + (rand.nextDouble() * (Math.pow(2,32) - Math.pow(2,31)));
}
@Override
protected Pair<Long, Double> getExistingValue(Double value)
{
return propertyValueDAO.getPropertyDoubleValue(value);
}
}
private class DateGen extends UniqueValueGenerator<Date>
{
@Override
protected Date createValue()
{
Random rand = new Random();
Date date = new Date(rand.nextLong());
// Dates are stored to day precision, make sure we return the
// same value that will be stored, for comparison in assert statements etc.
Date truncDate = PropertyDateValueEntity.truncateDate(date);
return truncDate;
}
@Override
protected Pair<Long, Date> getExistingValue(Date value)
{
return propertyValueDAO.getPropertyDateValue(value);
}
}
private class SerializableGen extends UniqueValueGenerator<Serializable>
{
@Override
protected Serializable createValue()
{
return new MySerializable();
}
@Override
protected Pair<Long, Serializable> getExistingValue(Serializable value)
{
return propertyValueDAO.getPropertyValue(value);
}
}
private static class MySerializable implements Serializable
{
private static final long serialVersionUID = 1L;
private final Long val;
public MySerializable()
{
val = rand.nextLong();
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((this.val == null) ? 0 : this.val.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
MySerializable other = (MySerializable) obj;
if (this.val == null)
{
if (other.val != null) return false;
}
else if (!this.val.equals(other.val)) return false;
return true;
}
}
/**
* Generate values that aren't currently in the properties tables. By trying random values
* several times until an unused value is used. This is to help avoid red builds, since the
* assumption by the orphaned property cleanup test is that the properties are not in use
* (otherwise they won't be cleaned up!)
*/
private abstract class UniqueValueGenerator<T>
{
private final int maxTries = 5;
protected abstract T createValue();
protected abstract Pair<Long, T> getExistingValue(T value);
public T getUnusedValue()
{
int tries = 0;
T value = null;
boolean exists = true;
while (exists)
{
if (++tries > maxTries)
{
throw new RuntimeException("Unable to generate unused value in " + maxTries + " tries.");
}
value = createValue();
assertNotNull("Value generator should not generate a null value, but did.", value);
// Make sure the value isn't already present in the properties tables.
exists = (getExistingValue(value) != null);
}
return value;
}
}
}

View File

@@ -44,6 +44,7 @@ import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.extensions.surf.util.ISO8601DateFormat;
/**
@@ -874,4 +875,84 @@ public class PropertyValueDAOTest extends TestCase
}, false);
assertEquals("ID-value pair incorrect", v2Pair, pair);
}
/**
* MNT-10067: use a script to delete the orphaned property values.
*/
public void testScriptCanDeleteUnusedProps()
{
PropValGenerator valueGen = new PropValGenerator(propertyValueDAO);
// Find some values to use in the test that aren't already in the property tables.
final String stringValue = valueGen.createUniqueString();
final Double doubleValue = valueGen.createUniqueDouble();
final Date dateValue = valueGen.createUniqueDate();
final Serializable serValue = valueGen.createUniqueSerializable();
// We'll keep a list of the DB IDs of the persisted values so we can later check they've been deleted.
final Map<Object, Long> persistedIDs = new HashMap<Object, Long>();
txnHelper.doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
persistedIDs.put(stringValue, propertyValueDAO.getOrCreatePropertyStringValue(stringValue).getFirst());
persistedIDs.put(doubleValue, propertyValueDAO.getOrCreatePropertyDoubleValue(doubleValue).getFirst());
persistedIDs.put(dateValue, propertyValueDAO.getOrCreatePropertyDateValue(dateValue).getFirst());
persistedIDs.put(serValue, propertyValueDAO.getOrCreatePropertyValue(serValue).getFirst());
return null;
}
});
// Run the clean-up script.
txnHelper.doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
// Check there are some persisted values to delete.
assertEquals(stringValue, propertyValueDAO.getPropertyStringValue(stringValue).getSecond());
assertEquals(doubleValue, propertyValueDAO.getPropertyDoubleValue(doubleValue).getSecond());
assertEquals(dateValue, propertyValueDAO.getPropertyDateValue(dateValue).getSecond());
// Serializable values are the odd-one-out; we can't query for them by value
// and no de-duplication is used during storage.
assertEquals(serValue, propertyValueDAO.getPropertyValueById(persistedIDs.get(serValue)).getSecond());
propertyValueDAO.cleanupUnusedValues();
return null;
}
});
// Check all the properties have been deleted.
txnHelper.doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
assertPropDeleted(propertyValueDAO.getPropertyStringValue(stringValue));
assertPropDeleted(propertyValueDAO.getPropertyDoubleValue(doubleValue));
// TODO: fix date deletion, not currently handled by CleanAlfPropTables.sql
// assertPropDeleted(propertyValueDAO.getPropertyDateValue(dateValue));
// Serializable values cannot be queried by value
try
{
propertyValueDAO.getPropertyValueById(persistedIDs.get(serValue));
fail(String.format("Persisted %s was not deleted, but should have been.",
serValue.getClass().getSimpleName()));
}
catch (DataIntegrityViolationException e)
{
// Good - it was deleted.
}
return null;
}
});
}
private void assertPropDeleted(Pair<Long, ?> value)
{
if (value != null)
{
String msg = String.format("Property value [%s=%s] should have been deleted by cleanup script.",
value.getSecond().getClass().getSimpleName(), value.getSecond());
fail(msg);
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2005-2014 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.domain.schema.script;
import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.sql.DataSource;
import org.alfresco.util.ApplicationContextHelper;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* Integration tests for the {@link ScriptBundleExecutorImpl} class.
*
* @author Matt Ward
*/
public class ScriptBundleExecutorImplIntegrationTest
{
private static ApplicationContext ctx;
private ScriptBundleExecutor bundleExecutor;
private DataSource dataSource;
private JdbcTemplate jdbcTmpl;
@BeforeClass
public static void setUpBeforeClass() throws Exception
{
String[] config = new String[] {
"classpath:alfresco/application-context.xml",
"classpath:scriptexec/script-exec-test.xml"
};
ctx = ApplicationContextHelper.getApplicationContext(config);
}
@Before
public void setUp() throws Exception
{
bundleExecutor = ctx.getBean("bundleExecutor", ScriptBundleExecutorImpl.class);
dataSource = ctx.getBean("dataSource", DataSource.class);
jdbcTmpl = new JdbcTemplate(dataSource);
}
@Test
public void canExecuteBundle()
{
bundleExecutor.exec("scriptexec/${db.script.dialect}/bundle", "script_a.sql", "script_b.sql", "script_c.sql");
String select = "select message from alf_test_bundle order by message asc";
List<String> res = jdbcTmpl.queryForList(select, String.class);
assertEquals(2, res.size());
// script_c deleted "script_a message 1"
assertEquals("script_a message 2", res.get(0));
assertEquals("script_b", res.get(1));
}
@Test
public void postScriptIsRunFinallyEvenAfterEarlierFailure()
{
// script_b.sql will fail
bundleExecutor.execWithPostScript("scriptexec/${db.script.dialect}/bundle2",
"post_script.sql", "script_a.sql", "script_b.sql");
String select = "select message from alf_test_bundle2 order by message asc";
List<String> res = jdbcTmpl.queryForList(select, String.class);
assertEquals(1, res.size());
// post_script deleted "script_a message 1"
assertEquals("script_a message 2", res.get(0));
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2005-2014 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.domain.schema.script;
import static org.junit.Assert.*;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.commons.logging.Log;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for the {@link ScriptBundleExecutorImpl} class.
*
* @author Matt Ward
*/
@RunWith(MockitoJUnitRunner.class)
public class ScriptBundleExecutorImplTest
{
// Class under test
private ScriptBundleExecutorImpl bundleExecutor;
private @Mock ScriptExecutor scriptExecutor;
private @Mock Log log;
@BeforeClass
public static void setUpClass() throws Exception
{
}
@Before
public void setUp() throws Exception
{
bundleExecutor = new ScriptBundleExecutorImpl(scriptExecutor);
bundleExecutor.log = log;
}
@Test
public void canExecuteMultipleScripts() throws Exception
{
bundleExecutor.exec("/path/to/script/dir", "one.sql", "two.sql", "three.sql");
InOrder inOrder = Mockito.inOrder(scriptExecutor);
inOrder.verify(scriptExecutor).executeScriptUrl("/path/to/script/dir/one.sql");
inOrder.verify(scriptExecutor).executeScriptUrl("/path/to/script/dir/two.sql");
inOrder.verify(scriptExecutor).executeScriptUrl("/path/to/script/dir/three.sql");
}
@Test
public void willAlwaysRunPostBatchScript() throws Exception
{
// The first of the "main" scripts will fail...
Exception e = new RuntimeException("Script failure!");
doThrow(e).when(scriptExecutor).executeScriptUrl("/path/to/script/dir/work01.sql");
bundleExecutor.execWithPostScript("/path/to/script/dir", "post.sql", "pre.sql", "work01.sql", "work02.sql");
InOrder inOrder = Mockito.inOrder(scriptExecutor);
inOrder.verify(scriptExecutor).executeScriptUrl("/path/to/script/dir/pre.sql");
inOrder.verify(scriptExecutor).executeScriptUrl("/path/to/script/dir/work01.sql");
// work02.sql will NOT be executed, but the post-script will be.
inOrder.verify(scriptExecutor, never()).executeScriptUrl("/path/to/script/dir/work02.sql");
inOrder.verify(scriptExecutor).executeScriptUrl("/path/to/script/dir/post.sql");
verify(log).error(anyString(), same(e));
}
}

View File

@@ -0,0 +1,158 @@
package org.alfresco.repo.domain.schema.script;
import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.sql.DataSource;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.ApplicationContextHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.MySQLInnoDBDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* Integration tests for the {@link ScriptExecutorImpl} class.
*
* @author Matt Ward
*/
public class ScriptExecutorImplIntegrationTest
{
private final static Log log = LogFactory.getLog(ScriptExecutorImplIntegrationTest.class);
private static ApplicationContext ctx;
private ScriptExecutor scriptExecutor;
private DataSource dataSource;
private JdbcTemplate jdbcTmpl;
private Dialect dialect;
@BeforeClass
public static void setUpBeforeClass() throws Exception
{
String[] config = new String[] {
"classpath:alfresco/application-context.xml",
"classpath:scriptexec/script-exec-test.xml"
};
ctx = ApplicationContextHelper.getApplicationContext(config);
}
@Before
public void setUp() throws Exception
{
scriptExecutor = ctx.getBean("simpleScriptExecutor", ScriptExecutorImpl.class);
dataSource = ctx.getBean("dataSource", DataSource.class);
dialect = ctx.getBean("dialect", Dialect.class);
jdbcTmpl = new JdbcTemplate(dataSource);
}
/**
* Check that we can execute a simple script, without any dialect-specific loading.
*
* @throws Exception
*/
@Test
public void canExecuteBasicScript() throws Exception
{
scriptExecutor.executeScriptUrl("scriptexec/basic.sql");
String select = "select textfield from alf_test_script_exec order by textfield asc";
List<String> res = jdbcTmpl.queryForList(select, String.class);
assertEquals(2, res.size());
assertEquals("hello", res.get(0));
assertEquals("world", res.get(1));
}
/**
* Check that a script designed to be run for all varieties of DBMS
* (i.e. in subdirectory org.hibernate.dialect.Dialect) will run
* regardless of specific dialect (e.g. MySQL or PostgreSQL)
*
* @throws Exception
*/
@Test
public void canExecuteGenericDialectScript() throws Exception
{
scriptExecutor.executeScriptUrl("scriptexec/${db.script.dialect}/generic.sql");
String select = "select message from alf_test_script_exec_generic";
List<String> res = jdbcTmpl.queryForList(select, String.class);
assertEquals(1, res.size());
assertEquals("generic", res.get(0));
}
/**
* Test the case of executing a specific (e.g. PostgreSQL) database script
* when no general script is present (therefore no overriding mechanism is required).
*
* @throws Exception
*/
@Test
public void canExecuteSpecificDialectScript() throws Exception
{
scriptExecutor.executeScriptUrl("scriptexec/${db.script.dialect}/specific.sql");
String select = "select message from alf_test_script_exec_specific";
List<String> res = jdbcTmpl.queryForList(select, String.class);
assertEquals(1, res.size());
if (dialect.getClass().equals(MySQLInnoDBDialect.class))
{
assertEquals("mysql", res.get(0));
}
else if (dialect.getClass().equals(PostgreSQLDialect.class))
{
assertEquals("postgresql", res.get(0));
}
else
{
log.warn("No suitable dialect-specific DB script for test canExecuteSpecificDialectScript()");
}
}
/**
* Test the case of executing a specific database script (e.g. PostgreSQL) when
* a more generic script also exists -- the more generic script is not run.
*
* @throws Exception
*/
@Test
public void canExecuteSpecificDialectOverridingGenericScript() throws Exception
{
scriptExecutor.executeScriptUrl("scriptexec/${db.script.dialect}/override.sql");
String select = "select message from alf_test_script_exec_override";
List<String> res = jdbcTmpl.queryForList(select, String.class);
assertEquals(1, res.size());
if (dialect.getClass().equals(MySQLInnoDBDialect.class))
{
assertEquals("mysql", res.get(0));
}
else if (dialect.getClass().equals(PostgreSQLDialect.class))
{
assertEquals("postgresql", res.get(0));
}
else
{
log.warn("No suitable dialect-specific DB script for test canExecuteSpecificDialectOverridingGenericScript()");
}
}
@Test()
public void exceptionThrownWhenNoMatchingScriptFound() throws Exception
{
try
{
scriptExecutor.executeScriptUrl("scriptexec/${db.script.dialect}/non-existent-file.sql");
}
catch (AlfrescoRuntimeException e)
{
assertEquals("schema.update.err.script_not_found", e.getMsgId());
}
}
}