Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud)

100845: Merged 5.0.N (5.0.2) to HEAD-BUG-FIX (5.1/Cloud)
      100756: Merged V4.2-BUG-FIX (4.2.5) to 5.0.N (5.0.2)
         99878: Merged V4.1-BUG-FIX (4.1.10) to V4.2-BUG-FIX (4.2.5)
            99807: Merged DEV to V4.1-BUG-FIX (4.1.10)
               99672: MNT-12150 : Centera Connector : isContentUrlSupported is returning false for the Centera store
                  - Unit test to demonstrate issue was implemented.
                  - Previously suggested fix was also merged.
               99781: MNT-12150 : Centera Connector : isContentUrlSupported is returning false for the Centera store
                  - Updated javadoc for new method in ContentDataDAO.
                  - Corrected logic of eager cleaner. If store is read-only content should not be deleted.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@100915 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2015-03-31 23:13:31 +00:00
parent 40fc727b0d
commit 17e4c1b55e
5 changed files with 147 additions and 4 deletions

View File

@@ -271,6 +271,21 @@
cd.id is null cd.id is null
</select> </select>
<!-- Get content URL entities that were kept after orphan cleanup failure -->
<select id="select_ContentUrlsKeepOrphaned" resultMap="result_ContentUrl">
<![CDATA[
select
cu.*
from
alf_content_url cu
left outer join alf_content_data cd on (cd.content_url_id = cu.id)
where
cd.id is null and
cu.orphan_time = 0 and
cu.orphan_time is not null
]]>
</select>
<!-- Update a specific mimetype --> <!-- Update a specific mimetype -->
<update id="update_Mimetype" parameterType="Mimetype"> <update id="update_Mimetype" parameterType="Mimetype">
update update

View File

@@ -266,11 +266,17 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter
int deleted = 0; int deleted = 0;
for (ContentStore store : stores) for (ContentStore store : stores)
{ {
// Bypass if the store is read-only or doesn't support the URL // Bypass if the store is read-only
if (!store.isWriteSupported() || !store.isContentUrlSupported(contentUrl)) if (!store.isWriteSupported())
{ {
continue; continue;
} }
// MNT-12150 fix, bypass if the store doesn't support the URL but mark as deleted
if (!store.isContentUrlSupported(contentUrl))
{
deleted++;
continue;
}
if (callListeners) if (callListeners)
{ {
// Call listeners // Call listeners

View File

@@ -108,13 +108,22 @@ public interface ContentDataDAO
* @param contentUrlHandler the callback object to process the rows * @param contentUrlHandler the callback object to process the rows
* @param maxOrphanTimeExclusive the maximum orphan time (exclusive) * @param maxOrphanTimeExclusive the maximum orphan time (exclusive)
* @param maxResults the maximum number of results (1 or greater) * @param maxResults the maximum number of results (1 or greater)
* @return Returns a list of orphaned content URLs ordered by ID
*/ */
void getContentUrlsOrphaned( void getContentUrlsOrphaned(
ContentUrlHandler contentUrlHandler, ContentUrlHandler contentUrlHandler,
Long maxOrphanTimeExclusive, Long maxOrphanTimeExclusive,
int maxResults); int maxResults);
/**
* Enumerate all available content URLs that were orphaned and cleanup for these urls failed
*
* @param contentUrlHandler the callback object to process the rows
* @param maxResults the maximum number of results (1 or greater)
*/
void getContentUrlsKeepOrphaned(
ContentUrlHandler contentUrlHandler,
int maxResults);
/** /**
* Delete a batch of content URL entities. * Delete a batch of content URL entities.
*/ */

View File

@@ -57,6 +57,7 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
private static final String SELECT_CONTENT_URL_BY_KEY = "alfresco.content.select_ContentUrlByKey"; private static final String SELECT_CONTENT_URL_BY_KEY = "alfresco.content.select_ContentUrlByKey";
private static final String SELECT_CONTENT_URL_BY_KEY_UNREFERENCED = "alfresco.content.select_ContentUrlByKeyUnreferenced"; private static final String SELECT_CONTENT_URL_BY_KEY_UNREFERENCED = "alfresco.content.select_ContentUrlByKeyUnreferenced";
private static final String SELECT_CONTENT_URLS_ORPHANED = "alfresco.content.select.select_ContentUrlsOrphaned"; private static final String SELECT_CONTENT_URLS_ORPHANED = "alfresco.content.select.select_ContentUrlsOrphaned";
private static final String SELECT_CONTENT_URLS_KEEP_ORPHANED = "alfresco.content.select_ContentUrlsKeepOrphaned";
private static final String SELECT_CONTENT_DATA_BY_ID = "alfresco.content.select_ContentDataById"; private static final String SELECT_CONTENT_DATA_BY_ID = "alfresco.content.select_ContentDataById";
private static final String SELECT_CONTENT_DATA_BY_NODE_AND_QNAME = "alfresco.content.select_ContentDataByNodeAndQName"; private static final String SELECT_CONTENT_DATA_BY_NODE_AND_QNAME = "alfresco.content.select_ContentDataByNodeAndQName";
private static final String SELECT_CONTENT_DATA_BY_NODE_IDS = "alfresco.content.select_ContentDataByNodeIds"; private static final String SELECT_CONTENT_DATA_BY_NODE_IDS = "alfresco.content.select_ContentDataByNodeIds";
@@ -160,6 +161,23 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
} }
} }
@SuppressWarnings("unchecked")
public void getContentUrlsKeepOrphaned(
final ContentUrlHandler contentUrlHandler,
final int maxResults)
{
List<ContentUrlEntity> results = (List<ContentUrlEntity>) template.selectList(SELECT_CONTENT_URLS_KEEP_ORPHANED,
new RowBounds(0, maxResults));
// Pass the result to the callback
for (ContentUrlEntity result : results)
{
contentUrlHandler.handle(
result.getId(),
result.getContentUrl(),
result.getOrphanTime());
}
}
public int updateContentUrlOrphanTime(Long id, Long orphanTime, Long oldOrphanTime) public int updateContentUrlOrphanTime(Long id, Long orphanTime, Long oldOrphanTime)
{ {
ContentUrlUpdateEntity contentUrlUpdateEntity = new ContentUrlUpdateEntity(); ContentUrlUpdateEntity contentUrlUpdateEntity = new ContentUrlUpdateEntity();

View File

@@ -26,6 +26,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -33,7 +34,10 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.UnsupportedContentUrlException; import org.alfresco.repo.content.UnsupportedContentUrlException;
import org.alfresco.repo.content.cleanup.ContentStoreCleaner.DeleteFailureAction;
import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.repo.domain.contentdata.ContentDataDAO; import org.alfresco.repo.domain.contentdata.ContentDataDAO;
import org.alfresco.repo.domain.contentdata.ContentDataDAO.ContentUrlHandler;
import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.lock.JobLockService;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -74,6 +78,7 @@ public class ContentStoreCleanerTest extends TestCase
private ContentStore store; private ContentStore store;
private ContentStoreCleanerListener listener; private ContentStoreCleanerListener listener;
private List<String> deletedUrls; private List<String> deletedUrls;
private ContentDataDAO contentDataDAO;
@Override @Override
public void setUp() throws Exception public void setUp() throws Exception
@@ -87,7 +92,7 @@ public class ContentStoreCleanerTest extends TestCase
jobLockService = serviceRegistry.getJobLockService(); jobLockService = serviceRegistry.getJobLockService();
TransactionService transactionService = serviceRegistry.getTransactionService(); TransactionService transactionService = serviceRegistry.getTransactionService();
DictionaryService dictionaryService = serviceRegistry.getDictionaryService(); DictionaryService dictionaryService = serviceRegistry.getDictionaryService();
ContentDataDAO contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO"); contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
// we need a store // we need a store
store = (ContentStore) ctx.getBean("fileContentStore"); store = (ContentStore) ctx.getBean("fileContentStore");
@@ -506,6 +511,96 @@ public class ContentStoreCleanerTest extends TestCase
cleaner.execute(); cleaner.execute();
} }
public void testMNT_12150()
{
eagerCleaner.setEagerOrphanCleanup(false);
// create content with binary data and delete it in the same transaction
final StoreRef storeRef = nodeService.createStore("test", getName() + "-" + GUID.generate());
RetryingTransactionCallback<ContentData> prepareCallback = new RetryingTransactionCallback<ContentData>()
{
public ContentData execute() throws Throwable
{
// Create some content
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(13);
properties.put(ContentModel.PROP_NAME, (Serializable)"test.txt");
NodeRef contentNodeRef = nodeService.createNode(
rootNodeRef,
ContentModel.ASSOC_CHILDREN,
ContentModel.ASSOC_CHILDREN,
ContentModel.TYPE_CONTENT,
properties).getChildRef();
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.putContent("INITIAL CONTENT");
ContentData contentData = writer.getContentData();
// Delete the first node, bypassing archive
nodeService.addAspect(contentNodeRef, ContentModel.ASPECT_TEMPORARY, null);
nodeService.deleteNode(contentNodeRef);
// Done
return contentData;
}
};
ContentData contentData = transactionService.getRetryingTransactionHelper().doInTransaction(prepareCallback);
List<ContentStore> stores = new ArrayList<ContentStore>(2);
stores.add(store);
// add another store which doesn't support any content url format
stores.add(new FileContentStore(ctx, store.getRootLocation())
{
@Override
public boolean isContentUrlSupported(String contentUrl)
{
return false;
}
});
// configure cleaner to keep failed orphaned content urls
eagerCleaner.setStores(stores);
eagerCleaner.setListeners(Collections.<ContentStoreCleanerListener>emptyList());
cleaner.setProtectDays(0);
cleaner.setDeletionFailureAction(DeleteFailureAction.KEEP_URL);
// fire the cleaner
cleaner.execute();
// Go through the orphaned urls again
final TreeMap<Long, String> keptUrlsById = new TreeMap<Long, String>();
final ContentUrlHandler contentUrlHandler = new ContentUrlHandler()
{
@Override
public void handle(Long id, String contentUrl, Long orphanTime)
{
keptUrlsById.put(id, contentUrl);
}
};
// look for any kept urls in database
RetryingTransactionCallback<Void> testCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
contentDataDAO.getContentUrlsKeepOrphaned(contentUrlHandler, 1000);
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
// check that orphaned url was deleted
for (String url : keptUrlsById.values())
{
if (url.equalsIgnoreCase(contentData.getContentUrl()))
{
fail("Failed to cleanup orphaned content: " + contentData.getContentUrl());
}
}
}
private class DummyCleanerListener implements ContentStoreCleanerListener private class DummyCleanerListener implements ContentStoreCleanerListener
{ {
public void beforeDelete(ContentStore store, String contentUrl) throws ContentIOException public void beforeDelete(ContentStore store, String contentUrl) throws ContentIOException