+ * 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) {} + } + } +} diff --git a/source/java/org/alfresco/repo/lock/LockBehaviourImplTest.java b/source/java/org/alfresco/repo/lock/LockBehaviourImplTest.java index 2a8807f3f7..1f7cece860 100644 --- a/source/java/org/alfresco/repo/lock/LockBehaviourImplTest.java +++ b/source/java/org/alfresco/repo/lock/LockBehaviourImplTest.java @@ -348,4 +348,46 @@ public class LockBehaviourImplTest extends BaseSpringTest // TODO check that delete is also working } + /** + * ALF-5680: It is possible to cut/paste a locked file + */ + public void testCannotMoveNodeWhenLocked() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Create the node that we'll try to move + NodeRef parentNode = this.nodeRef; + ChildAssociationRef childAssocRef = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTENT); + + NodeRef nodeRef = childAssocRef.getChildRef(); + // Lock it - so that it can't be moved. + this.lockService.lock(nodeRef, LockType.WRITE_LOCK); + + // Create the new container that we'll move the node to. + NodeRef newParentRef = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER).getChildRef(); + + // Now the bad user will try to move the node. + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + try + { + nodeService.moveNode( + nodeRef, + newParentRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest")); + fail("Shouldn't have been able to move locked node."); + } + catch (NodeLockedException e) + { + // Good, we can't move it - as expected. + } + } } diff --git a/source/java/org/alfresco/repo/lock/LockServiceImpl.java b/source/java/org/alfresco/repo/lock/LockServiceImpl.java index 50c9146c5d..c4102635fc 100644 --- a/source/java/org/alfresco/repo/lock/LockServiceImpl.java +++ b/source/java/org/alfresco/repo/lock/LockServiceImpl.java @@ -67,6 +67,7 @@ public class LockServiceImpl implements LockService, NodeServicePolicies.OnCreateChildAssociationPolicy, NodeServicePolicies.BeforeUpdateNodePolicy, NodeServicePolicies.BeforeDeleteNodePolicy, + NodeServicePolicies.OnMoveNodePolicy, CopyServicePolicies.OnCopyNodePolicy, VersionServicePolicies.BeforeCreateVersionPolicy, VersionServicePolicies.OnCreateVersionPolicy @@ -124,6 +125,10 @@ public class LockServiceImpl implements LockService, NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, ContentModel.ASPECT_LOCKABLE, new JavaBehaviour(this, "beforeDeleteNode")); + this.policyComponent.bindClassBehaviour( + NodeServicePolicies.OnMoveNodePolicy.QNAME, + ContentModel.ASPECT_LOCKABLE, + new JavaBehaviour(this, "onMoveNode")); // Register copy class behaviour this.policyComponent.bindClassBehaviour( @@ -637,4 +642,11 @@ public class LockServiceImpl implements LockService, "\" +@\\{http\\://www.alfresco.org/model/content/1.0\\}" + ContentModel.PROP_LOCK_OWNER.getLocalName() + ":\"" + getUserName() + "\"" + " +@\\{http\\://www.alfresco.org/model/content/1.0\\}" + ContentModel.PROP_LOCK_TYPE.getLocalName() + ":\"" + lockType.toString() + "\""); } + + @Override + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + NodeRef nodeRef = oldChildAssocRef.getChildRef(); + checkForLock(nodeRef); + } } diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java index 21580fb38f..5fb2441938 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java @@ -718,28 +718,54 @@ public class ModuleManagementTool implements LogOutput } /** - * Outputs a message the console (in verbose mode) and the logger. + * Outputs a message the console (in verbose mode). * * @param message the message to output */ private void outputMessage(String message) { - outputMessage(message, false); + outputMessage(message, false, false); } /** - * Outputs a message the console (in verbose mode) and the logger. + * Outputs a message the console (in verbose mode). * * @param message the message to output - * @prarm indent indicates that the message should be formated with an indent + */ + private void outputErrorMessage(String message) + { + outputMessage(message, false, true); + } + + /** + * Outputs a message the console (in verbose mode). + * + * @param message the message to output + * @param indent indicates that the message should be formated with an indent */ private void outputMessage(String message, boolean indent) + { + outputMessage(message, indent, false); + } + + /** + * Outputs a message the console. Errors are always output, but others are only output in verbose mode. + * + * @param message the message to output + * @param indent indicates that the message should be formated with an indent + * @param error indicates that the message is an error. + */ + private void outputMessage(String message, boolean indent, boolean error) { if (indent == true) { message = " - " + message; } - if (this.verbose == true) + if (error) + { + System.err.println(message); + } + else if (this.verbose == true) { System.out.println(message); } @@ -857,7 +883,7 @@ public class ModuleManagementTool implements LogOutput catch (ModuleManagementToolException e) { // These are user-friendly - manager.outputMessage(e.getMessage()); + manager.outputErrorMessage(e.getMessage()); outputUsage(); System.exit(ERROR_EXIT_CODE); } diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java index 52957f09b3..c1a04b5545 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java @@ -259,7 +259,7 @@ public class ModuleManagementToolTest extends TestCase { manager.setVerbose(true); - String warLocation = getFileLocation(".war", "module/test.war"); + String warLocation = getFileLocation(".war", "module/test.war"); //Version 4.0.1 String ampLocation = getFileLocation(".amp", "module/test_v4.amp"); try diff --git a/source/java/org/alfresco/repo/module/tool/WarHelperImpl.java b/source/java/org/alfresco/repo/module/tool/WarHelperImpl.java index e16446b998..606aaed429 100644 --- a/source/java/org/alfresco/repo/module/tool/WarHelperImpl.java +++ b/source/java/org/alfresco/repo/module/tool/WarHelperImpl.java @@ -40,7 +40,7 @@ public class WarHelperImpl implements WarHelper if (propsFile != null && propsFile.exists()) { Properties warVers = loadProperties(propsFile); - VersionNumber warVersion = new VersionNumber(warVers.getProperty("version.major")+"."+warVers.getProperty("version.revision")+"."+warVers.getProperty("version.minor")); + VersionNumber warVersion = new VersionNumber(warVers.getProperty("version.major")+"."+warVers.getProperty("version.minor")+"."+warVers.getProperty("version.revision")); if(warVersion.compareTo(installingModuleDetails.getRepoVersionMin())==-1) { throw new ModuleManagementToolException("The module ("+installingModuleDetails.getTitle()+") must be installed on a repo version greater than "+installingModuleDetails.getRepoVersionMin()); } diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java index 571df0f6ec..b8aa1e988c 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java @@ -47,6 +47,8 @@ public class SolrBackupClient implements InitializingBean private JobLockService jobLockService; private String remoteBackupLocation; + + private int numberToKeep; private String core; @@ -72,6 +74,14 @@ public class SolrBackupClient implements InitializingBean { this.remoteBackupLocation = remoteBackupLocation; } + + /** + * @param numberToKeep the numberToKeep to set + */ + public void setNumberToKeep(int numberToKeep) + { + this.numberToKeep = numberToKeep; + } public void execute() { @@ -132,6 +142,7 @@ public class SolrBackupClient implements InitializingBean params.set("qt", "/"+core+"/replication"); params.set("command", "backup"); params.set("location", remoteBackupLocation); + params.set("numberToKeep", numberToKeep); QueryResponse response = solrAdminClient.query(params); diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java b/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java index 5f8978759d..e1c3261bd5 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java @@ -43,21 +43,47 @@ public class SolrChildApplicationContextFactory extends ChildApplicationContextF private static String ALFRESCO_LAG = "tracker.alfresco.lag"; private static String ALFRESCO_LAG_DURATION = "tracker.alfresco.lag.duration"; + + private static String ALFRESCO_LAST_INDEXED_TXN = "tracker.alfresco.last.indexed.txn"; + + private static String ALFRESCO_APPROX_TXNS_REMAINING = "tracker.alfresco.approx.txns.remaining"; + + private static String ALFRESCO_APPROX_INDEXING_TIME_REMAINING = "tracker.alfresco.approx.indexing.time.remaining"; private static String ARCHIVE_ACTIVE = "tracker.archive.active"; private static String ARCHIVE_LAG = "tracker.archive.lag"; private static String ARCHIVE_LAG_DURATION = "tracker.archive.lag.duration"; + + private static String ARCHIVE_LAST_INDEXED_TXN = "tracker.archive.last.indexed.txn"; + + private static String ARCHIVE_APPROX_TXNS_REMAINING = "tracker.archive.approx.txns.remaining"; + + private static String ARCHIVE_APPROX_INDEXING_TIME_REMAINING = "tracker.archive.approx.indexing.time.remaining"; + + + @Override public boolean isUpdateable(String name) { // TODO Auto-generated method stub return super.isUpdateable(name) - && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_ACTIVE) && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_LAG) - && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_LAG_DURATION) && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_ACTIVE) - && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_LAG) && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_LAG_DURATION); + && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_ACTIVE) + && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_LAG) + && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_LAG_DURATION) + && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_LAST_INDEXED_TXN) + && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_APPROX_TXNS_REMAINING) + && !name.equals(SolrChildApplicationContextFactory.ALFRESCO_APPROX_INDEXING_TIME_REMAINING) + + && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_ACTIVE) + && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_LAG) + && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_LAG_DURATION) + && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_APPROX_TXNS_REMAINING) + && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_APPROX_INDEXING_TIME_REMAINING) + && !name.equals(SolrChildApplicationContextFactory.ARCHIVE_LAST_INDEXED_TXN) + ; } @Override @@ -81,12 +107,18 @@ public class SolrChildApplicationContextFactory extends ChildApplicationContextF String alfrescoLag = alfresco.getString("Lag"); String alfrescoActive = alfresco.getString("Active"); String alfrescoDuration = alfresco.getString("Duration"); + String alfrescoLastIndexedTxn = alfresco.getString("Id for last TX in index"); + String alfrescoApproxTxnsReminaing = alfresco.getString("Approx transactions remaining"); + String alfrescoApproxIndexingTimeReminaing = alfresco.getString("Approx indexing time remaining"); JSONObject archive = summary.getJSONObject("archive"); String archiveLag = archive.getString("Lag"); String archiveActive = archive.getString("Active"); String archiveDuration = archive.getString("Duration"); + String archiveLastIndexedTxn = archive.getString("Id for last TX in index"); + String archiveApproxTxnsReminaing = archive.getString("Approx transactions remaining"); + String archiveApproxIndexingTimeReminaing = archive.getString("Approx indexing time remaining"); if (name.equals(SolrChildApplicationContextFactory.ALFRESCO_ACTIVE)) { @@ -100,6 +132,18 @@ public class SolrChildApplicationContextFactory extends ChildApplicationContextF { return alfrescoDuration; } + else if (name.equals(SolrChildApplicationContextFactory.ALFRESCO_LAST_INDEXED_TXN)) + { + return alfrescoLastIndexedTxn; + } + else if (name.equals(SolrChildApplicationContextFactory.ALFRESCO_APPROX_TXNS_REMAINING)) + { + return alfrescoApproxTxnsReminaing; + } + else if (name.equals(SolrChildApplicationContextFactory.ALFRESCO_APPROX_INDEXING_TIME_REMAINING)) + { + return alfrescoApproxIndexingTimeReminaing; + } else if (name.equals(SolrChildApplicationContextFactory.ARCHIVE_ACTIVE)) { return archiveActive; @@ -112,6 +156,18 @@ public class SolrChildApplicationContextFactory extends ChildApplicationContextF { return archiveDuration; } + else if (name.equals(SolrChildApplicationContextFactory.ARCHIVE_LAST_INDEXED_TXN)) + { + return archiveLastIndexedTxn; + } + else if (name.equals(SolrChildApplicationContextFactory.ARCHIVE_APPROX_TXNS_REMAINING)) + { + return archiveApproxTxnsReminaing; + } + else if (name.equals(SolrChildApplicationContextFactory.ARCHIVE_APPROX_INDEXING_TIME_REMAINING)) + { + return archiveApproxIndexingTimeReminaing; + } else { return "Unavailable"; @@ -139,9 +195,16 @@ public class SolrChildApplicationContextFactory extends ChildApplicationContextF result.add(SolrChildApplicationContextFactory.ALFRESCO_ACTIVE); result.add(SolrChildApplicationContextFactory.ALFRESCO_LAG); result.add(SolrChildApplicationContextFactory.ALFRESCO_LAG_DURATION); + result.add(SolrChildApplicationContextFactory.ALFRESCO_LAST_INDEXED_TXN); + result.add(SolrChildApplicationContextFactory.ALFRESCO_APPROX_TXNS_REMAINING); + result.add(SolrChildApplicationContextFactory.ALFRESCO_APPROX_INDEXING_TIME_REMAINING); + result.add(SolrChildApplicationContextFactory.ARCHIVE_ACTIVE); result.add(SolrChildApplicationContextFactory.ARCHIVE_LAG); result.add(SolrChildApplicationContextFactory.ARCHIVE_LAG_DURATION); + result.add(SolrChildApplicationContextFactory.ARCHIVE_LAST_INDEXED_TXN); + result.add(SolrChildApplicationContextFactory.ARCHIVE_APPROX_TXNS_REMAINING); + result.add(SolrChildApplicationContextFactory.ARCHIVE_APPROX_INDEXING_TIME_REMAINING); result.addAll(super.getPropertyNames()); return result; } diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java index ebb9167a26..818c3092c8 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java @@ -151,4 +151,10 @@ public interface SOLRTrackingComponent * @return */ public Long getMaxTxnCommitTime(); + + /** + * Get the last transaction id from the repo + * @return + */ + public Long getMaxTxnId(); } diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java index 3f06a6a396..2329a607aa 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java @@ -886,4 +886,12 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent nodeDAO.setCheckNodeConsistency(); return nodeDAO.getMaxTxnCommitTime(); } + + @Override + public Long getMaxTxnId() + { + long maxCommitTime = System.currentTimeMillis()+1L; + nodeDAO.setCheckNodeConsistency(); + return nodeDAO.getMaxTxnIdByCommitTime(maxCommitTime); + } }