diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/audit-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/audit-common-SqlMap.xml index 5d822884be..a42f26a4a1 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/audit-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/audit-common-SqlMap.xml @@ -201,6 +201,14 @@ sp_mpl.value_prop_id = #searchValueId# + + order by + entry.id asc + + + order by + entry.id desc + @@ -242,6 +250,14 @@ sp_mpl.value_prop_id = #searchValueId# + + order by + entry.id asc + + + order by + entry.id desc + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/audit/AuditComponent.java b/source/java/org/alfresco/repo/audit/AuditComponent.java index 7af005e1b6..93c581944a 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponent.java +++ b/source/java/org/alfresco/repo/audit/AuditComponent.java @@ -188,6 +188,8 @@ public interface AuditComponent * Get the audit entries that match the given criteria. * * @param callback the callback that will handle results + * @param forward true for results to ordered from first to last, + * or false to order from last to first * @param applicationName if not null, find entries logged against this application * @param user if not null, find entries logged against this user * @param from the start search time (null to start at the beginning) @@ -198,6 +200,7 @@ public interface AuditComponent */ void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, int maxResults); @@ -205,6 +208,8 @@ public interface AuditComponent * Get the audit entries that match the given criteria. * * @param callback the callback that will handle results + * @param forward true for results to ordered from first to last, + * or false to order from last to first * @param applicationName if not null, find entries logged against this application * @param user if not null, find entries logged against this user * @param from the start search time (null to start at the beginning) @@ -217,6 +222,7 @@ public interface AuditComponent */ void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, String searchKey, Serializable searchValue, int maxResults); diff --git a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java index 0457e34cb0..919ed9c85a 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java @@ -1323,6 +1323,7 @@ public class AuditComponentImpl implements AuditComponent */ public void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, @@ -1338,7 +1339,8 @@ public class AuditComponentImpl implements AuditComponent return; } - auditDAO.findAuditEntries(callback, applicationName, user, from, to, maxResults); + auditDAO.findAuditEntries( + callback, forward, applicationName, user, from, to, maxResults); } /** @@ -1346,6 +1348,7 @@ public class AuditComponentImpl implements AuditComponent */ public void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, @@ -1362,6 +1365,7 @@ public class AuditComponentImpl implements AuditComponent return; } - auditDAO.findAuditEntries(callback, applicationName, user, from, to, searchKey, searchValue, maxResults); + auditDAO.findAuditEntries( + callback, forward, applicationName, user, from, to, searchKey, searchValue, maxResults); } } diff --git a/source/java/org/alfresco/repo/audit/AuditComponentTest.java b/source/java/org/alfresco/repo/audit/AuditComponentTest.java index e01667fbeb..9c46fa6238 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentTest.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentTest.java @@ -35,6 +35,7 @@ import java.util.Map; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.audit.model.AuditApplication; import org.alfresco.repo.audit.model.AuditModelException; import org.alfresco.repo.audit.model.AuditModelRegistry; @@ -328,11 +329,16 @@ public class AuditComponentTest extends TestCase rowCount.setValue(rowCount.intValue() + 1); return true; } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new AlfrescoRuntimeException(errorMsg, error); + } }; sb.delete(0, sb.length()); rowCount.setValue(0); - auditComponent.auditQuery(callback, APPLICATION_ACTIONS_TEST, null, null, null, -1); + auditComponent.auditQuery(callback, true, APPLICATION_ACTIONS_TEST, null, null, null, -1); assertTrue("Expected some data", rowCount.intValue() > 0); logger.debug(sb.toString()); int allResults = rowCount.intValue(); @@ -340,21 +346,21 @@ public class AuditComponentTest extends TestCase // Limit by count sb.delete(0, sb.length()); rowCount.setValue(0); - auditComponent.auditQuery(callback, APPLICATION_ACTIONS_TEST, null, null, null, 1); + auditComponent.auditQuery(callback, true, APPLICATION_ACTIONS_TEST, null, null, null, 1); assertEquals("Expected to limit data", 1, rowCount.intValue()); logger.debug(sb.toString()); // Limit by time and query up to and excluding the 'before' time sb.delete(0, sb.length()); rowCount.setValue(0); - auditComponent.auditQuery(callback, APPLICATION_ACTIONS_TEST, null, null, beforeTime, -1); + auditComponent.auditQuery(callback, true, APPLICATION_ACTIONS_TEST, null, null, beforeTime, -1); logger.debug(sb.toString()); int resultsBefore = rowCount.intValue(); // Limit by time and query from and including the 'before' time sb.delete(0, sb.length()); rowCount.setValue(0); - auditComponent.auditQuery(callback, APPLICATION_ACTIONS_TEST, null, beforeTime, null, -1); + auditComponent.auditQuery(callback, true, APPLICATION_ACTIONS_TEST, null, beforeTime, null, -1); logger.debug(sb.toString()); int resultsAfter = rowCount.intValue(); @@ -364,13 +370,13 @@ public class AuditComponentTest extends TestCase sb.delete(0, sb.length()); rowCount.setValue(0); - auditComponent.auditQuery(callback, APPLICATION_ACTIONS_TEST, user, null, null, -1); + auditComponent.auditQuery(callback, true, APPLICATION_ACTIONS_TEST, user, null, null, -1); assertTrue("Expected some data for specific user", rowCount.intValue() > 0); logger.debug(sb.toString()); sb.delete(0, sb.length()); rowCount.setValue(0); - auditComponent.auditQuery(callback, APPLICATION_ACTIONS_TEST, "Numpty", null, null, -1); + auditComponent.auditQuery(callback, true, APPLICATION_ACTIONS_TEST, "Numpty", null, null, -1); assertTrue("Expected no data for bogus user", rowCount.intValue() == 0); logger.debug(sb.toString()); @@ -483,12 +489,17 @@ public class AuditComponentTest extends TestCase ; return true; } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new AlfrescoRuntimeException(errorMsg, error); + } }; auditService.clearAudit(APPLICATION_API_TEST); results.clear(); sb.delete(0, sb.length()); - auditService.auditQuery(auditQueryCallback, APPLICATION_API_TEST, null, null, null, -1); + auditService.auditQuery(auditQueryCallback, true, APPLICATION_API_TEST, null, null, null, -1); logger.debug(sb.toString()); assertTrue("There should be no audit entries for the API test after a clear", results.isEmpty()); @@ -522,7 +533,7 @@ public class AuditComponentTest extends TestCase // Check that the call was audited results.clear(); sb.delete(0, sb.length()); - auditService.auditQuery(auditQueryCallback, APPLICATION_API_TEST, null, null, null, -1); + auditService.auditQuery(auditQueryCallback, true, APPLICATION_API_TEST, null, null, null, -1); logger.debug(sb.toString()); assertFalse("Did not get any audit results after successful login", results.isEmpty()); @@ -539,7 +550,7 @@ public class AuditComponentTest extends TestCase } results.clear(); sb.delete(0, sb.length()); - auditService.auditQuery(auditQueryCallback, APPLICATION_API_TEST, null, null, null, -1); + auditService.auditQuery(auditQueryCallback, true, APPLICATION_API_TEST, null, null, null, -1); logger.debug(sb.toString()); assertFalse("Did not get any audit results after failed login", results.isEmpty()); } diff --git a/source/java/org/alfresco/repo/audit/AuditServiceImpl.java b/source/java/org/alfresco/repo/audit/AuditServiceImpl.java index 57e8168b15..5f494dd4cf 100644 --- a/source/java/org/alfresco/repo/audit/AuditServiceImpl.java +++ b/source/java/org/alfresco/repo/audit/AuditServiceImpl.java @@ -173,13 +173,15 @@ public class AuditServiceImpl implements AuditService */ public void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, int maxResults) { ParameterCheck.mandatory("callback", callback); - auditComponent.auditQuery(callback, applicationName, user, from, to, maxResults); + auditComponent.auditQuery( + callback, forward, applicationName, user, from, to, maxResults); } /** @@ -188,6 +190,7 @@ public class AuditServiceImpl implements AuditService */ public void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, String searchKey, Serializable searchValue, int maxResults) @@ -195,6 +198,7 @@ public class AuditServiceImpl implements AuditService { ParameterCheck.mandatory("callback", callback); - auditComponent.auditQuery(callback, applicationName, user, from, to, searchKey, searchValue, maxResults); + auditComponent.auditQuery( + callback, forward, applicationName, user, from, to, searchKey, searchValue, maxResults); } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java index 092612f318..2808904585 100644 --- a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java +++ b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java @@ -740,6 +740,7 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO, */ public void findAuditEntries( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, int maxResults) { @@ -754,6 +755,7 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO, */ public void findAuditEntries( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, String searchKey, Serializable searchValue, int maxResults) diff --git a/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java b/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java index d08c096708..839a5699f5 100644 --- a/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java @@ -383,7 +383,10 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO if (auditValuesPair == null) { // Ignore - logger.warn("Audit entry not joined to audit properties: " + row); + more = callback.handleAuditEntryError( + row.getAuditEntryId(), + "Audit entry not joined to audit properties: " + row, + null); return; } auditValues = (Map) auditValuesPair.getSecond(); @@ -397,17 +400,26 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO } catch (ClassCastException e) { - logger.warn("Audit entry not linked to a Map value: " + row); + more = callback.handleAuditEntryError( + row.getAuditEntryId(), + "Audit entry not linked to a Map value: " + row, + e); return; } catch (Throwable e) { - logger.warn("Audit entry unable to extract audited values: " + row, e); + more = callback.handleAuditEntryError( + row.getAuditEntryId(), + "Audit entry unable to extract audited values: " + row, + e); return; } if (auditValues == null) { - logger.warn("Audit entry incompletely joined to audit properties: " + row); + more = callback.handleAuditEntryError( + row.getAuditEntryId(), + "Audit entry incompletely joined to audit properties: " + row, + null); return; } } @@ -430,25 +442,28 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO public void findAuditEntries( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, int maxResults) { AuditQueryRowHandler rowHandler = new AuditQueryRowHandler(callback); - findAuditEntries(rowHandler, applicationName, user, from, to, maxResults, null, null); + findAuditEntries(rowHandler, forward, applicationName, user, from, to, maxResults, null, null); } public void findAuditEntries( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, String searchKey, Serializable searchValue, int maxResults) { AuditQueryRowHandler rowHandler = new AuditQueryRowHandler(callback); - findAuditEntries(rowHandler, applicationName, user, from, to, maxResults, searchKey, searchValue); + findAuditEntries(rowHandler, forward, applicationName, user, from, to, maxResults, searchKey, searchValue); } protected abstract void findAuditEntries( AuditQueryRowHandler rowHandler, + boolean forward, String applicationName, String user, Long from, Long to, int maxResults, String searchKey, Serializable searchValue); } diff --git a/source/java/org/alfresco/repo/domain/audit/AuditDAO.java b/source/java/org/alfresco/repo/domain/audit/AuditDAO.java index e35d29573b..9f2e01b9e1 100644 --- a/source/java/org/alfresco/repo/domain/audit/AuditDAO.java +++ b/source/java/org/alfresco/repo/domain/audit/AuditDAO.java @@ -207,6 +207,8 @@ public interface AuditDAO * Find audit entries using the given parameters, any of which may be null * * @param callback the data callback per entry + * @param forward true for results to ordered from first to last, + * or false to order from last to first * @param applicationName the name of the application to search against (optional) * @param user the user to search for (optional) * @param from the minimum entry time (optional) @@ -215,6 +217,7 @@ public interface AuditDAO */ void findAuditEntries( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, int maxResults); /** @@ -230,6 +233,7 @@ public interface AuditDAO */ void findAuditEntries( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, String searchKey, Serializable searchValue, int maxResults); diff --git a/source/java/org/alfresco/repo/domain/audit/AuditDAOTest.java b/source/java/org/alfresco/repo/domain/audit/AuditDAOTest.java index 6ca982ede9..28e7062de3 100644 --- a/source/java/org/alfresco/repo/domain/audit/AuditDAOTest.java +++ b/source/java/org/alfresco/repo/domain/audit/AuditDAOTest.java @@ -28,10 +28,12 @@ import java.io.File; import java.io.Serializable; import java.net.URL; import java.util.Collections; +import java.util.LinkedList; import java.util.Map; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; import org.alfresco.repo.domain.audit.AuditDAO.AuditApplicationInfo; import org.alfresco.repo.domain.contentdata.ContentDataDAO; @@ -43,6 +45,7 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.Pair; +import org.apache.commons.lang.mutable.MutableInt; import org.springframework.context.ConfigurableApplicationContext; /** @@ -182,11 +185,13 @@ public class AuditDAOTest extends TestCase return appName; } - public void testAuditQuery() throws Exception + public synchronized void testAuditQuery() throws Exception { // Some entries doAuditEntryImpl(1); + final MutableInt count = new MutableInt(0); + final LinkedList timestamps = new LinkedList(); // Find everything, but look for a specific key final AuditQueryCallback callback = new AuditQueryCallback() { @@ -197,20 +202,55 @@ public class AuditDAOTest extends TestCase long time, Map values) { - System.out.println(values); + count.setValue(count.intValue() + 1); + timestamps.add(time); return true; } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new AlfrescoRuntimeException(errorMsg, error); + } }; RetryingTransactionCallback findCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { - auditDAO.findAuditEntries(callback, null, null, null, null, "/a/b/c", null, -1); + auditDAO.findAuditEntries(callback, true, null, null, null, null, "/a/b/c", null, 2); return null; } }; + count.setValue(0); + timestamps.clear(); txnHelper.doInTransaction(findCallback); + assertTrue("Expected at least one result", count.intValue() > 0); + + // Make sure that the last two entries are in forward order (ascending time) + Long lastTimestamp = timestamps.removeLast(); + Long secondLastTimeStamp = timestamps.removeLast(); + assertTrue("The timestamps should be in ascending order", lastTimestamp.compareTo(secondLastTimeStamp) > 0); + + // Make sure that the last two entries differ in time + wait(1000L); + + // Search in reverse order + doAuditEntryImpl(1); + RetryingTransactionCallback findReverseCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + auditDAO.findAuditEntries(callback, false, null, null, null, null, "/a/b/c", null, 2); + return null; + } + }; + timestamps.clear(); + txnHelper.doInTransaction(findReverseCallback); + + // Make sure that the last two entries are in reverse order (descending time) + lastTimestamp = timestamps.removeLast(); + secondLastTimeStamp = timestamps.removeLast(); + assertTrue("The timestamps should be in descending order", lastTimestamp.compareTo(secondLastTimeStamp) < 0); } public void testAuditDeleteEntries() throws Exception @@ -227,6 +267,11 @@ public class AuditDAOTest extends TestCase fail("Expected no results. All entries should have been removed."); return false; } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new AlfrescoRuntimeException(errorMsg, error); + } }; // Some entries @@ -239,7 +284,7 @@ public class AuditDAOTest extends TestCase Long appId = auditDAO.getAuditApplication(appName).getId(); auditDAO.deleteAuditEntries(appId, null, null); // There should be no entries - auditDAO.findAuditEntries(noResultsCallback, appName, null, null, null, -1); + auditDAO.findAuditEntries(noResultsCallback, true, appName, null, null, null, -1); return null; } }; diff --git a/source/java/org/alfresco/repo/domain/audit/AuditQueryParameters.java b/source/java/org/alfresco/repo/domain/audit/AuditQueryParameters.java index ea200ab562..1a6f5adbee 100644 --- a/source/java/org/alfresco/repo/domain/audit/AuditQueryParameters.java +++ b/source/java/org/alfresco/repo/domain/audit/AuditQueryParameters.java @@ -34,7 +34,7 @@ import java.util.Date; */ public class AuditQueryParameters { - private Long auditEntryId; + private boolean forward; private Long auditAppNameId; private Long auditUserId; private Long auditFromTime; @@ -51,25 +51,30 @@ public class AuditQueryParameters { StringBuilder sb = new StringBuilder(512); sb.append("AuditEntryParameters") - .append("[ auditEntryId=").append(auditEntryId) + .append("[ forward=").append(forward) .append(", auditAppNameId=").append(auditAppNameId) .append(", auditUserId=").append(auditUserId) - .append(", auditFromTime").append(auditFromTime == null ? null : new Date(auditFromTime)) - .append(", auditToTime").append(auditToTime == null ? null : new Date(auditToTime)) - .append(", searchKeyId").append(searchKeyId) - .append(", searchValueId").append(searchValueId) + .append(", auditFromTime=").append(auditFromTime == null ? null : new Date(auditFromTime)) + .append(", auditToTime=").append(auditToTime == null ? null : new Date(auditToTime)) + .append(", searchKeyId=").append(searchKeyId) + .append(", searchValueId=").append(searchValueId) .append("]"); return sb.toString(); } - public Long getAuditEntryId() + public boolean isForward() { - return auditEntryId; + return forward; } - public void setAuditEntryId(Long entryId) + public void setForward(boolean forward) { - this.auditEntryId = entryId; + this.forward = forward; + } + + public boolean isForwardTrue() + { + return true; } public Long getAuditAppNameId() diff --git a/source/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java b/source/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java index db92801fd9..c64ebb19fc 100644 --- a/source/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; import org.alfresco.ibatis.RollupRowHandler; import org.alfresco.repo.domain.audit.AbstractAuditDAOImpl; @@ -194,6 +193,7 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl @Override protected void findAuditEntries( final AuditQueryRowHandler rowHandler, + boolean forward, String appName, String user, Long from, Long to, int maxResults, String searchKey, Serializable searchValue) { @@ -244,12 +244,13 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl } params.setSearchValueId(searchValuePair.getFirst()); } + params.setForward(forward); if (maxResults > 0) { // Query without getting the values. We gather all the results and batch-fetch the audited // values afterwards. - final TreeMap resultsByValueId = new TreeMap(); + final Map resultsByValueId = new HashMap(173); PropertyFinderCallback propertyFinderCallback = new PropertyFinderCallback() { public void handleProperty(Long id, Serializable value) @@ -264,7 +265,6 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl { // The handler will deal with the entry } - rowHandler.processResult(row); } }; @@ -288,6 +288,11 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl List valueIds = new ArrayList(resultsByValueId.keySet()); propertyValueDAO.getPropertiesByIds(valueIds, propertyFinderCallback); } + // Now pass the filled-out results to the row handler (order-preserved) + for (AuditQueryResult row : rows) + { + rowHandler.processResult(row); + } } else { diff --git a/source/java/org/alfresco/service/cmr/audit/AuditService.java b/source/java/org/alfresco/service/cmr/audit/AuditService.java index b173e37b93..037a397538 100644 --- a/source/java/org/alfresco/service/cmr/audit/AuditService.java +++ b/source/java/org/alfresco/service/cmr/audit/AuditService.java @@ -168,12 +168,24 @@ public interface AuditService String user, long time, Map values); + + /** + * Handle audit entry failures + * + * @param entryId the entry ID + * @param errorMsg the error message + * @param error the exception causing the error (may be null) + * @return Return true to continue processing rows or false to stop + */ + boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error); } /** * Get the audit entries that match the given criteria. * * @param callback the callback that will handle results + * @param forward true for results to ordered from first to last, + * or false to order from last to first * @param applicationName if not null, find entries logged against this application * @param user if not null, find entries logged against this user * @param from the start search time (null to start at the beginning) @@ -184,6 +196,7 @@ public interface AuditService */ void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, int maxResults); @@ -191,6 +204,8 @@ public interface AuditService * Get the audit entries that match the given criteria. * * @param callback the callback that will handle results + * @param forward true for results to ordered from first to last, + * or false to order from last to first * @param applicationName if not null, find entries logged against this application * @param user if not null, find entries logged against this user * @param from the start search time (null to start at the beginning) @@ -203,6 +218,7 @@ public interface AuditService */ void auditQuery( AuditQueryCallback callback, + boolean forward, String applicationName, String user, Long from, Long to, String searchKey, Serializable searchValue, int maxResults);