mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged V4.0-BUG-FIX to HEAD
33935: ALF-12854: SOLR - requires more detailed tracking information - Part 1: info reported for the SOLR SUMMARY action 33936: ALF-11693: Treat logging.properties the same way as log4j.properties and excluded it from the core .jar 33945: ALF-12867: RIP jmx-dumper.jar 33946: Fix minor issue introduced in r33920 33947: IT-6847 "Error displays on recent 4.0 TS upgrade while navigate to a document details page" - errors spotted on folder details & in document library list also resolved by this fix 33951: ALF-12867: Fixed typo 33955: Some unit tests and examples on how to implement file wiping or shredding - One API change: On EagerContentStoreCleaner, the following is now protected: protected boolean deleteFromStore(String contentUrl, ContentStore store) - Examples in unit test ContentStoreCleanerTest: testForcedImmediateShredding and testShreddingCleanup 33962: RIP hyperic plugin 33965: ALF-12697: Asynchronously get RSS items for dashlets on page load 33969: Fix for ALF-12307 Solr backup fills the disk - number to Keep can be set by property/subsystem/Share admin - set default backup localtion 33970: Fix for ALF-12854 SOLR - requires more detailed tracking information Part 2: basic stats added to JMX and improved statistical moment calculation 33984: Update 4.0.1 installers to use Tomcat 6.0.35 - Remembered to set useHttpOnly on <Context> - Also commented out JreMemoryLeakPreventionListener 33988: ALF-12717 CIFS: Unfriendly message occurs when a user with consumer/contributor role tries to rename space. 33997: ALF-12697: Remove async WebScript from dashlet family 33999: Fix for ALF-12854 SOLR - requires more detailed tracking information - Final part - Expose via share admin 34005: Fix for ALF-12708 34007: Merged V3.4-BUG-FIX (3.4.9) to 3.4-BUG-FIX (4.0.1) 34006: Merged V3.4 (3.4.8) to V3.4-BUG-FIX (3.4.9) 34002: ALF-12898: MMT does not calculate the current version number correctly and does not report errors - ModuleManagementTool was constructing the wrong current war file version number. The minor and revision values were the wrong way around. 3.4.8 became 3.8.4 - Modified unit test to use the actual version number in the test war 4.1.0 rather than 4.0.1 - Modified the ModuleManagementTool so that problems would be reported to the user even if they did not have -verbose set 34016: Update version.revision for 4.0.1 34022: Merged V3.4-BUG-FIX to V4.0-BUG-FIX 33952: ALF-5680: It is possible to cut/paste a locked file 34003: Merged DEV to V3.4-BUG-FIX 34001: ALF-12709: CLONE - Run action Wizard. Can't run action. Fixed regression connected with separator of AVM path in .NodeRef. id that has been changed from .;. to .|. 34023: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY) 33912: ALF-9899: Merged HEAD to V3.4-BUG-FIX (PARTIAL) 31912: Merged DEV to HEAD 31823: TransactionalCache provides REPEATABLE READ - Values found in shared cache are placed into transactional cache - Previously, values could keep changing until first write (READ COMMITTED) but now the first read sets the value until it is changed by the current transaction 33981: Updated version.revision for 3.4.9 33985: ALF-12364: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33984: Update 3.4.9 installers to use Tomcat 6.0.35 - Remembered to set useHttpOnly on <Context> - Also commented out JreMemoryLeakPreventionListener git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@34024 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -370,21 +370,142 @@ public class ContentStoreCleanerTest extends TestCase
|
||||
assertTrue("Content listener was not called", deletedUrls.contains(reader.getContentUrl()));
|
||||
}
|
||||
|
||||
public void testProtectedRemoval() throws Exception
|
||||
/**
|
||||
* Test forced and immediate shredding of content
|
||||
* <p/>
|
||||
* There is no validation that the file is affected. It's an example of how to wire
|
||||
* different listeners together to do neat things to the files before deletion.
|
||||
*/
|
||||
public void testForcedImmediateShredding() throws Exception
|
||||
{
|
||||
cleaner.setProtectDays(1);
|
||||
// add some content to the store
|
||||
ContentWriter writer = store.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
|
||||
writer.putContent("ABC");
|
||||
String contentUrl = writer.getContentUrl();
|
||||
// An example of an eager cleaner that will wipe files before deleting their contents
|
||||
// This is very much like a listener, but listeners are only called by the standard,
|
||||
// scheduled cleaner.
|
||||
final Set<String> wipedUrls = new HashSet<String>(3);
|
||||
final EagerContentStoreCleaner wipingEagerCleaner = new EagerContentStoreCleaner()
|
||||
{
|
||||
final FileWipingContentCleanerListener fileWiper = new FileWipingContentCleanerListener();
|
||||
@Override
|
||||
protected boolean deleteFromStore(String contentUrl, ContentStore store)
|
||||
{
|
||||
fileWiper.beforeDelete(store, contentUrl);
|
||||
wipedUrls.add(contentUrl);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
wipingEagerCleaner.setStores(Collections.singletonList(store));
|
||||
/*
|
||||
* Note that we don't need to wire the 'wipingEagerCleaner' into anything.
|
||||
* You can if you want it to wipe for all use cases. In this case, we're just
|
||||
* going to manually force it to clean.
|
||||
*/
|
||||
|
||||
// fire the cleaner
|
||||
// Create a node with content
|
||||
final StoreRef storeRef = nodeService.createStore("test", getName() + "-" + GUID.generate());
|
||||
RetryingTransactionCallback<NodeRef> testCallback = new RetryingTransactionCallback<NodeRef>()
|
||||
{
|
||||
public NodeRef 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");
|
||||
|
||||
// Done
|
||||
return contentNodeRef;
|
||||
}
|
||||
};
|
||||
final NodeRef contentNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
|
||||
|
||||
// Now, force the node to be deleted and make sure it gets cleaned up directly
|
||||
// This can be used where some sensitive data has been identified and, before deletion,
|
||||
// the URL can be marked for immediate cleanup (in the post-commit phase, of course!)
|
||||
RetryingTransactionCallback<String> deleteCallback = new RetryingTransactionCallback<String>()
|
||||
{
|
||||
public String execute() throws Throwable
|
||||
{
|
||||
// Let's pretend we're in 'beforeDeleteNode'
|
||||
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
||||
String contentUrl = reader.getContentUrl();
|
||||
wipingEagerCleaner.registerOrphanedContentUrl(contentUrl, true);
|
||||
|
||||
nodeService.deleteNode(contentNodeRef);
|
||||
// Done
|
||||
return contentUrl;
|
||||
}
|
||||
};
|
||||
String contentUrl = transactionService.getRetryingTransactionHelper().doInTransaction(deleteCallback);
|
||||
|
||||
// So, we don't fire the cleaner, but notice that the eager cleaner has 'wiped' the content
|
||||
assertTrue("Expected our URL to have been wiped.", wipedUrls.contains(contentUrl));
|
||||
cleaner.execute();
|
||||
|
||||
// the content should have disappeared as it is not in the database
|
||||
assertTrue("Protected content was deleted", store.exists(contentUrl));
|
||||
assertFalse("Content listener was called with deletion of protected URL", deletedUrls.contains(contentUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test basic wiping of file contents on normal orphan cleanup
|
||||
*/
|
||||
public void testShreddingCleanup() throws Exception
|
||||
{
|
||||
eagerCleaner.setEagerOrphanCleanup(false);
|
||||
cleaner.setProtectDays(0);
|
||||
|
||||
// Add in a the Wiping cleaner listener
|
||||
FileWipingContentCleanerListener fileWiper = new FileWipingContentCleanerListener();
|
||||
List<ContentStoreCleanerListener> listeners = new ArrayList<ContentStoreCleanerListener>(1);
|
||||
listeners.add(fileWiper);
|
||||
eagerCleaner.setListeners(listeners);
|
||||
|
||||
// Create a node with content
|
||||
final StoreRef storeRef = nodeService.createStore("test", getName() + "-" + GUID.generate());
|
||||
RetryingTransactionCallback<NodeRef> testCallback = new RetryingTransactionCallback<NodeRef>()
|
||||
{
|
||||
public NodeRef 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");
|
||||
|
||||
// Done
|
||||
return contentNodeRef;
|
||||
}
|
||||
};
|
||||
final NodeRef contentNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
|
||||
|
||||
// Simple delete
|
||||
RetryingTransactionCallback<Void> deleteCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
nodeService.deleteNode(contentNodeRef);
|
||||
// Done
|
||||
return null;
|
||||
}
|
||||
};
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(deleteCallback);
|
||||
|
||||
// It's orphaned now. Fire the cleaner.
|
||||
cleaner.execute();
|
||||
}
|
||||
|
||||
private class DummyCleanerListener implements ContentStoreCleanerListener
|
||||
{
|
||||
public void beforeDelete(ContentStore store, String contentUrl) throws ContentIOException
|
||||
|
@@ -143,6 +143,8 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter
|
||||
* <p/>
|
||||
* <b>NB: </b>Any content registered <u>will</u> be deleted if the current transaction
|
||||
* commits and if 'eager' cleanup is turned on.
|
||||
* <p/>
|
||||
* Note that listeners are not called for this process.
|
||||
*
|
||||
* @return Returns <tt>true</tt> if the content was scheduled for post-transaction deletion.
|
||||
* If the return value is <tt>true</tt> then the calling code <b>must</b> delete
|
||||
@@ -157,7 +159,9 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter
|
||||
* Queues orphaned content for post-transaction removal
|
||||
* <p/>
|
||||
* <b>NB: </b>Any content registered <u>will</u> be deleted if the current transaction
|
||||
* commits and if 'eager' cleanup is turned on.
|
||||
* commits and if 'eager' cleanup is turned on OR if 'force' is <tt>true</tt>.
|
||||
* <p/>
|
||||
* Note that listeners are not called for this process.
|
||||
*
|
||||
* @param force <tt>true</tt> for force the post-commit URL deletion
|
||||
* regardless of the setting {@link #setEagerOrphanCleanup(boolean)}.
|
||||
@@ -246,6 +250,8 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter
|
||||
|
||||
/**
|
||||
* Delete the content URL from all stores
|
||||
* <p/>
|
||||
* Note that listeners <b>are</b> called for this process.
|
||||
*
|
||||
* @param contentUrl the URL to delete
|
||||
* @return Returns <tt>true</tt> if all deletes were successful
|
||||
@@ -298,7 +304,7 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter
|
||||
/**
|
||||
* Attempts to delete the URL from the store, catching and reporing errors.
|
||||
*/
|
||||
private boolean deleteFromStore(String contentUrl, ContentStore store)
|
||||
protected boolean deleteFromStore(String contentUrl, ContentStore store)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 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.content.cleanup;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.repo.content.filestore.FileContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Simple listener that overwrites files with zeros.
|
||||
* <p/>
|
||||
* Wire this into the {@link EagerContentStoreCleaner} as a listener and it will
|
||||
* ensure that files have their contents overwritten with zeros before deletion.
|
||||
* Note that this process does not affect the content lifecycyle in any way
|
||||
* i.e. content will still follow the same orphan path as before.
|
||||
* <p>
|
||||
* Clearly wiring this up with a {@link DeletedContentBackupCleanerListener} is
|
||||
* pointless as you will be making a copy of the before wiping it or end up
|
||||
* copying a file full of zero depending on the order of the listeners.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0.1
|
||||
*/
|
||||
public class FileWipingContentCleanerListener implements ContentStoreCleanerListener
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(FileWipingContentCleanerListener.class);
|
||||
|
||||
public FileWipingContentCleanerListener()
|
||||
{
|
||||
}
|
||||
|
||||
public void beforeDelete(ContentStore sourceStore, String contentUrl) throws ContentIOException
|
||||
{
|
||||
// First check if the content is present at all
|
||||
ContentReader reader = sourceStore.getReader(contentUrl);
|
||||
if (reader != null && reader.exists())
|
||||
{
|
||||
// Call to implementation's shred
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(
|
||||
"About to shread: \n" +
|
||||
" URL: " + contentUrl + "\n" +
|
||||
" Source: " + sourceStore);
|
||||
}
|
||||
try
|
||||
{
|
||||
shred(reader);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error(
|
||||
"Content shredding failed: \n" +
|
||||
" URL: " + contentUrl + "\n" +
|
||||
" Source: " + sourceStore + "\n" +
|
||||
" Reader: " + reader,
|
||||
e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error(
|
||||
"Content no longer exists. Unable to shred: \n" +
|
||||
" URL: " + contentUrl + "\n" +
|
||||
" Source: " + sourceStore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to perform shredding on disparate forms of readers. This implementation will,
|
||||
* by default, identify more specific readers and make calls for those.
|
||||
*
|
||||
* @param reader the reader to the content needing shredding
|
||||
* @exception IOException any IO error
|
||||
*/
|
||||
protected void shred(ContentReader reader) throws IOException
|
||||
{
|
||||
if (reader instanceof FileContentReader)
|
||||
{
|
||||
FileContentReader fileReader = (FileContentReader) reader;
|
||||
File file = fileReader.getFile();
|
||||
shred(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link #shred(ContentReader)} when the reader points to a physical file.
|
||||
* The default implementation simply overwrites the content with zeros.
|
||||
*
|
||||
* @param file the file to shred before deletion
|
||||
* @throws IOException any IO error
|
||||
*/
|
||||
protected void shred(File file) throws IOException
|
||||
{
|
||||
// Double check
|
||||
if (!file.exists() || !file.canWrite())
|
||||
{
|
||||
throw new ContentIOException("Unable to write to file: " + file);
|
||||
}
|
||||
long bytes = file.length();
|
||||
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
|
||||
try
|
||||
{
|
||||
/*
|
||||
* There are many more efficient ways of writing bytes into the file.
|
||||
* However, it is likely that implementations will do a lot more than
|
||||
* just overwrite with zeros.
|
||||
*/
|
||||
for (int i = 0; i < bytes; i++)
|
||||
{
|
||||
os.write(0);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try {os.close(); } catch (Throwable e) {}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user