mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-08 14:51:49 +00:00
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:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
@@ -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();
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user