/*
* 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 .
*/
package org.alfresco.repo.domain.propval;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* @see PropertyValueDAO#cleanupUnusedValues()
*
* @author Derek Hulley
* @since 5.1
*/
@Category(OwnJVMTestsCategory.class)
public class PropertyValueCleanupTest
{
private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private TransactionService transactionService;
private AttributeService attributeService;
private RetryingTransactionHelper txnHelper;
private PropertyValueDAO propertyValueDAO;
@Before
public void setUp() throws Exception
{
transactionService = (TransactionService) ctx.getBean("TransactionService");
txnHelper = transactionService.getRetryingTransactionHelper();
txnHelper.setMaxRetries(0);
attributeService = (AttributeService) ctx.getBean("AttributeService");
propertyValueDAO = (PropertyValueDAO) ctx.getBean("propertyValueDAO");
// Remove the caches to test all functionality
clearCaches();
}
private void clearCaches()
{
((AbstractPropertyValueDAOImpl)propertyValueDAO).clearCaches();
}
@Test
public synchronized void testRapidCreationDuringCleanup() throws Exception
{
// Create and delete some well-known attributes
String toDeleteKey1 = "testRapidCreationDuringCleanup";
String toDeleteKey2 = UUID.randomUUID().toString();
byte[] toDeleteProp = new String("Key is " + toDeleteKey2).getBytes("US-ASCII");
assertNull("Did not expect to find the attribute ", attributeService.getAttribute(toDeleteKey1, toDeleteKey2));
assertNull("Key2 should be NOT present as a property value", propertyValueDAO.getPropertyStringValue(toDeleteKey2));
attributeService.createAttribute(toDeleteProp, toDeleteKey1, toDeleteKey2);
assertNotNull("Did not find the attribute ", attributeService.getAttribute(toDeleteKey1, toDeleteKey2));
// Check that we can get hold of the underlying property
assertNotNull("Key2 should be present as a property value", propertyValueDAO.getPropertyStringValue(toDeleteKey2));
// Delete the attribute
attributeService.removeAttribute(toDeleteKey1, toDeleteKey2);
// Check that we can STILL get hold of the underlying property
assertNotNull("Key2 should be present as a property value (even if unreferenced)", propertyValueDAO.getPropertyStringValue(toDeleteKey2));
// Start threads that throw stuff into the AttributeService
ThreadGroup threadGroup = new ThreadGroup("testRapidCreationDuringCleanup");
InsertSerializableAttributes[] runnables = new InsertSerializableAttributes[2];
for (int i = 0; i < runnables.length; i++)
{
// Runnable
runnables[i] = new InsertSerializableAttributes();
// Put it in a thread
String threadName = "" + i;
Thread thread = new Thread(threadGroup, runnables[i], threadName);
thread.setDaemon(true); // Precautionary
// Start it
thread.start();
}
// Wait a bit for data to really get in there
wait(1000L);
// Now run the cleanup script
propertyValueDAO.cleanupUnusedValues();
// Stop the threads
for (int i = 0; i < runnables.length; i++)
{
// Runnable
runnables[i].running.set(false);
}
// Clear any caches
clearCaches();
// The cleanup should have removed the key2
assertNull("Key2 should be NOT present as a property value (cleanup job)", propertyValueDAO.getPropertyStringValue(toDeleteKey2));
// Now check that all the properties written can still be retrieved
for (int i = 0; i < runnables.length; i++)
{
// Runnable
String key1 = runnables[i].key1;
String key2 = runnables[i].key2;
List key3s = new ArrayList(runnables[i].key3s); // Copy entire list
for (Integer key3 : key3s)
{
// Get the attribute
byte[] propFetched = (byte[]) attributeService.getAttribute(key1, key2, key3);
assertTrue(
"Arrays were not equal for " + key1 + ", " + key2 + ", " + key3,
Arrays.equals(runnables[i].prop, propFetched));
}
}
}
/**
* Simple runnable that continuously creates new serializable attributes until stopped.
* Each thread has a unique second key value, a sequential third key and generates serializable (unshared)
* property values.
*
* @author Derek Hulley
*/
private class InsertSerializableAttributes implements Runnable
{
private final String key1;
private final String key2;
private List key3s;
private final byte[] prop;
private AtomicBoolean running = new AtomicBoolean(true);
private int counter = 0;
private InsertSerializableAttributes() throws UnsupportedEncodingException
{
key1 = "PropertyValueCleanupTest";
key2 = UUID.randomUUID().toString();
key3s = Collections.synchronizedList(new ArrayList(200));
prop = new String("Key is " + key2).getBytes("US-ASCII");
}
@Override
public synchronized void run()
{
while (running.get())
{
Integer key3 = Integer.valueOf(counter);
attributeService.createAttribute(prop, key1, key2, key3);
// Record the successful addition
key3s.add(key3);
// Increment the counter
counter++;
// Wait a bit so that we don't drown the test
try { wait(10L); } catch (InterruptedException e) {}
}
}
}
}