diff --git a/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/patch/v33/RMv33HoldAuditEntryValuesPatchUnitTest.java b/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/patch/v33/RMv33HoldAuditEntryValuesPatchUnitTest.java index e89f055e453..207c4e5f99e 100644 --- a/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/patch/v33/RMv33HoldAuditEntryValuesPatchUnitTest.java +++ b/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/patch/v33/RMv33HoldAuditEntryValuesPatchUnitTest.java @@ -33,14 +33,15 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO; -import org.alfresco.repo.domain.propval.PropertyStringValueEntity; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO; +import org.alfresco.repo.domain.propval.PropertyStringValueEntity; + /** * RM V3.3 Hold audit entries values patch unit test * @@ -55,7 +56,6 @@ public class RMv33HoldAuditEntryValuesPatchUnitTest @InjectMocks private RMv33HoldAuditEntryValuesPatch patch; - @Before public void setUp() { @@ -89,19 +89,20 @@ public void holdAuditEntriesAreUpdatedAfterUpgrade() verify(mockedRecordsManagementQueryDAO, times(1)).updatePropertyStringValueEntity(addToHoldPropertyStringValueEntity); verify(mockedRecordsManagementQueryDAO, times(1)).getPropertyStringValueEntity("removeFromHold"); verify(mockedRecordsManagementQueryDAO, times(1)).updatePropertyStringValueEntity(removeFromHoldPropertyStringValueEntity); + verify(mockedRecordsManagementQueryDAO, times(1)).getPropertyStringValueEntity("deleteHold"); verify(mockedRecordsManagementQueryDAO, times(1)).updatePropertyStringValueEntity(deleteHoldPropertyStringValueEntity); assertEquals("Add To Hold", addToHoldPropertyStringValueEntity.getStringValue()); - assertEquals("add to hold", addToHoldPropertyStringValueEntity.getStringLower()); + assertEquals("add to hold", addToHoldPropertyStringValueEntity.getStringEndLower()); assertEquals(Long.valueOf(770_786_109L), addToHoldPropertyStringValueEntity.getStringCrc()); assertEquals("Remove From Hold", removeFromHoldPropertyStringValueEntity.getStringValue()); - assertEquals("remove from hold", removeFromHoldPropertyStringValueEntity.getStringLower()); + assertEquals("remove from hold", removeFromHoldPropertyStringValueEntity.getStringEndLower()); assertEquals(Long.valueOf(2_967_613_012L), removeFromHoldPropertyStringValueEntity.getStringCrc()); assertEquals("Delete Hold", deleteHoldPropertyStringValueEntity.getStringValue()); - assertEquals("delete hold", deleteHoldPropertyStringValueEntity.getStringLower()); + assertEquals("delete hold", deleteHoldPropertyStringValueEntity.getStringEndLower()); assertEquals(Long.valueOf(132_640_810L), deleteHoldPropertyStringValueEntity.getStringCrc()); } @@ -123,5 +124,3 @@ public void patchRunWithSuccessWhenNoHoldEntries() verify(mockedRecordsManagementQueryDAO, times(0)).updatePropertyStringValueEntity(any()); } } - - diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/AuditImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/AuditImpl.java index d2259e3c6cf..81149b8efcb 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/AuditImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/AuditImpl.java @@ -85,7 +85,7 @@ public class AuditImpl implements Audit // list of equals filter's auditEntry (via where clause) private final static Set LIST_AUDIT_ENTRY_EQUALS_QUERY_PROPERTIES = new HashSet<>( - Arrays.asList(new String[] { CREATED_BY_USER, VALUES_KEY, VALUES_VALUE })); + Arrays.asList(new String[]{CREATED_BY_USER, VALUES_KEY, VALUES_VALUE})); // map of sort parameters for the moment one createdAt private final static Map SORT_PARAMS_TO_NAMES; @@ -295,23 +295,29 @@ public CollectionWithPagingInfo listAuditEntries(String auditAppId, } else { - if (hasMoreItems) { - if (q != null) { + if (hasMoreItems) + { + if (q != null) + { // filtering via "where" clause AuditEntryQueryWalker propertyWalker = new AuditEntryQueryWalker(); QueryHelper.walk(q, propertyWalker); totalItems = getAuditEntriesCountByAppAndProperties(auditApplication, propertyWalker); - } else { + } + else + { totalItems = getAuditEntriesCountByApp(auditApplication); } - } else { + } + else + { totalItems = totalRetrievedItems; } } entriesAudit = (skipCount >= totalRetrievedItems) - ? Collections.emptyList() - : entriesAudit.subList(skipCount, end); + ? Collections.emptyList() + : entriesAudit.subList(skipCount, end); return CollectionWithPagingInfo.asPaged(paging, entriesAudit, hasMoreItems, totalItems); } @@ -475,8 +481,7 @@ public List getQueryResultAuditEntries(AuditService.AuditApplication final Map mapUserInfo = new HashMap<>(10); // create the callback for auditQuery method - final AuditQueryCallback callback = new AuditQueryCallback() - { + final AuditQueryCallback callback = new AuditQueryCallback() { public boolean valuesRequired() { return ((includeParam != null) && (includeParam.contains(PARAM_INCLUDE_VALUES))); @@ -532,7 +537,7 @@ public AuditEntry getAuditEntry(String auditAppId, long auditEntryId, Parameters params.setApplicationName(auditApplication.getName()); params.setFromId(auditEntryId); params.setToId(auditEntryId + 1); - + List includeParam = new ArrayList<>(); if (parameters != null) { @@ -545,8 +550,7 @@ public AuditEntry getAuditEntry(String auditAppId, long auditEntryId, Parameters final List results = new ArrayList<>(); // create the callback for auditQuery method - final AuditQueryCallback callback = new AuditQueryCallback() - { + final AuditQueryCallback callback = new AuditQueryCallback() { public boolean valuesRequired() { return ((includeParam != null) && (includeParam.contains(PARAM_INCLUDE_VALUES))); @@ -710,8 +714,7 @@ private void validateWhereBetween(String auditAppId, Long from, Long to) @Override public CollectionWithPagingInfo listAuditEntriesByNodeId(String nodeId, Parameters parameters) { - AuthenticationUtil.runAs(new RunAsWork() - { + AuthenticationUtil.runAs(new RunAsWork() { public Void doWork() throws Exception { checkEnabled(); @@ -772,8 +775,7 @@ private List getQueryResultAuditEntriesByNodeRef(NodeRef nodeRef, Au final List results = new ArrayList<>(); String auditAppId = "alfresco-access"; - String auditApplicationName = AuthenticationUtil.runAs(new RunAsWork() - { + String auditApplicationName = AuthenticationUtil.runAs(new RunAsWork() { public String doWork() throws Exception { return findAuditAppByIdOr404(auditAppId).getName(); @@ -781,8 +783,7 @@ public String doWork() throws Exception }, AuthenticationUtil.getSystemUserName()); // create the callback for auditQuery method - final AuditQueryCallback callback = new AuditQueryCallback() - { + final AuditQueryCallback callback = new AuditQueryCallback() { public boolean valuesRequired() { return ((includeParam != null) && (includeParam.contains(PARAM_INCLUDE_VALUES))); @@ -808,8 +809,7 @@ public boolean handleAuditEntry(Long entryId, String applicationName, String use Long toTime = propertyWalker.getToTime(); validateWhereBetween(nodeRef.getId(), fromTime, toTime); - AuthenticationUtil.runAs(new RunAsWork() - { + AuthenticationUtil.runAs(new RunAsWork() { public Object doWork() throws Exception { // QueryParameters @@ -820,7 +820,7 @@ public Object doWork() throws Exception pathParams.setFromTime(fromTime); pathParams.setToTime(toTime); pathParams.setApplicationName(auditApplicationName); - pathParams.addSearchKey("/"+auditAppId+"/transaction/path", nodePath); + pathParams.addSearchKey("/" + auditAppId + "/transaction/path", nodePath); auditService.auditQuery(callback, pathParams, limit); AuditQueryParameters copyFromPathParams = new AuditQueryParameters(); @@ -830,7 +830,7 @@ public Object doWork() throws Exception copyFromPathParams.setFromTime(fromTime); copyFromPathParams.setToTime(toTime); copyFromPathParams.setApplicationName(auditApplicationName); - copyFromPathParams.addSearchKey("/"+auditAppId+"/transaction/copy/from/path", nodePath); + copyFromPathParams.addSearchKey("/" + auditAppId + "/transaction/copy/from/path", nodePath); auditService.auditQuery(callback, copyFromPathParams, limit); AuditQueryParameters moveFromPathParams = new AuditQueryParameters(); @@ -840,7 +840,7 @@ public Object doWork() throws Exception moveFromPathParams.setFromTime(fromTime); moveFromPathParams.setToTime(toTime); moveFromPathParams.setApplicationName(auditApplicationName); - moveFromPathParams.addSearchKey("/"+auditAppId+"/transaction/move/from/path", nodePath); + moveFromPathParams.addSearchKey("/" + auditAppId + "/transaction/move/from/path", nodePath); auditService.auditQuery(callback, moveFromPathParams, limit); return null; @@ -857,7 +857,7 @@ private class AuditEntriesByNodeIdQueryWalker extends MapBasedQueryWalker public AuditEntriesByNodeIdQueryWalker() { - super(new HashSet<>(Arrays.asList(new String[] { CREATED_BY_USER })), null); + super(new HashSet<>(Arrays.asList(new String[]{CREATED_BY_USER})), null); } @Override @@ -909,14 +909,16 @@ public int getAuditEntriesCountByApp(AuditService.AuditApplication auditApplicat public int getAuditEntriesCountByAppAndProperties(AuditService.AuditApplication auditApplication, AuditEntryQueryWalker propertyWalker) { + final String applicationName = auditApplication.getKey().substring(1); AuditQueryParameters parameters = new AuditQueryParameters(); - parameters.setApplicationName(auditApplication.getName()); + parameters.setApplicationName(applicationName); parameters.setFromTime(propertyWalker.getFromTime()); parameters.setToTime(propertyWalker.getToTime()); parameters.setFromId(propertyWalker.getFromId()); parameters.setToId(propertyWalker.getToId()); + parameters.setUser(propertyWalker.getCreatedByUser()); - return auditService.getAuditEntriesCountByAppAndProperties(parameters); + return auditService.getAuditEntriesCountByAppAndProperties(applicationName, parameters); } } diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java b/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java index 29955432a7b..0279ff28451 100644 --- a/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java @@ -1,286 +1,283 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.repo.audit; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.audit.model.AuditApplication; -import org.alfresco.repo.audit.model.AuditModelRegistry; -import org.alfresco.repo.audit.model._3.AuditPath; -import org.alfresco.service.cmr.audit.AuditQueryParameters; -import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; - -/** - * The audit component. Used by the AuditService and AuditMethodInterceptor to insert audit entries. - * - * @author Derek Hulley - */ -public interface AuditComponent -{ - /** - * Determines whether audit is globally enabled or disabled. - * - * @return Returns true if audit is enabled - * - * @since 3.3 - */ - public boolean isAuditEnabled(); - - /** - * Switch auditing on or off - * - * @param enable true to enable auditing or false to disable - * - * @since 3.4 - */ - public void setAuditEnabled(boolean enable); - - /** - * @param userAuditFilter UserAuditFilter - * - * @since 4.2 - */ - public void setUserAuditFilter(UserAuditFilter userAuditFilter); - - /** - * Get all registered audit applications, whether active or not. - * - * @return Returns a map of registered audit applications keyed by name - * - * @since 3.4 - */ - public Map getAuditApplications(); - - /** - * Determine whether the audit infrastructure expects audit values to be passed in. - * This is a helper method to allow optimizations in the client code. Reasons why - * this method might return false are: auditing is disabled; no audit applications - * have been registered. Sometimes, depending on the log level, this method may always - * return true. - * - * - * @return Returns true if the calling code (data producers) - * should go ahead and generate the data for - * {@link #recordAuditValues(String, Map) recording}. - * - * @since 3.3 - */ - public boolean areAuditValuesRequired(); - - /** - * Determines if audit values are required based on whether there are any audit applications - * registered to record data for the given path. This helper method gives data producers a - * shortcut in the event that nothing would be recorded in any event. - * - * @param path the audit path - * @return Returns true if there is at least one audit application - * registered to handle the given path. - * - * @since 3.4 - */ - public boolean areAuditValuesRequired(String path); - - /** - * Delete audit entries for the given application and time range - * - * @param applicationName the name of the application - * @param fromTime the start time of entries to remove (inclusive and optional) - * @param toTime the end time of entries to remove (exclusive and optional) - * @return Returns the number of entries deleted - * - * @since 3.2 - */ - int deleteAuditEntries(String applicationName, Long fromTime, Long toTime); - - /** - * Delete audit entries for the given application and id range - * - * @param applicationName the name of the application - * @param fromId the start time of entries to remove (inclusive and optional) - * @param toId the end time of entries to remove (exclusive and optional) - * @return Returns the number of entries deleted - * - * @since 5.2.2 - */ - int deleteAuditEntriesByIdRange(String applicationName, Long fromId, Long toId); - - /** - * Delete a discrete list of audit entries based on ID - * - * @param auditEntryIds the audit entry IDs to delete - * @return Returns the number of entries deleted - */ - int deleteAuditEntries(List auditEntryIds); - - /** - * Check if an audit path is enabled. The path will be disabled if it or any higher - * path has been explicitly disabled. Any disabled path will not be processed when - * data is audited. - * - * @param applicationName the name of the application being logged to - * @param path the audit path to check or null to assume the - * application's root path - * @return Returns true if the audit path has been disabled - * - * @since 3.2 - */ - boolean isAuditPathEnabled(String applicationName, String path); - - /** - * Enable auditing (if it is not already enabled) for all paths that contain the given path. - * The path is the path as originally logged and - * not the path that the generated data may contain - although this would be similarly - * enabled. - *

- * If the enabled - * - * @param applicationName the name of the application being logged to - * @param path the audit path to check or null to assume the - * application's root path - * - * @since 3.2 - */ - void enableAudit(String applicationName, String path); - - /** - * Disable auditing (if it is not already disabled) for all paths that contain the given path. - * The path is the path as originally logged and - * not the path that the generated data may contain - although this would be similarly - * disabled. - *

- * If the path is /x/y then any data paths that start with /x/y will be stripped - * out before data generators and data recorders are applied. If the path represents - * the root path of the application, then auditing for that application is effectively disabled. - * - * @param applicationName the name of the application being logged to - * @param path the audit path to check or null to assume the - * application's root path - * - * @since 3.2 - */ - void disableAudit(String applicationName, String path); - - /** - * Remove all disabled paths i.e. enable all per-path based auditing. Auditing may still be - * disabled globally. This is primarily for test purposes; applications should know which - * paths need {@link #enableAudit(String, String) enabling} or - * {@link #disableAudit(String, String) disabled}. - * - * @param applicationName the name of the application - * - * @since 3.2 - */ - void resetDisabledPaths(String applicationName); - - /** - * Create an audit entry for the given map of values. The map key is a path - starting with '/' - * ({@link AuditApplication#AUDIT_PATH_SEPARATOR}) - relative to the root path provided. - *

- * The root path and value keys are combined to produce a map of data keyed by full path. This - * fully-pathed map is then passed through the - * {@link AuditModelRegistry#getAuditPathMapper() audit path mapper}. The result may yield data - * destined for several different - * {@link AuditModelRegistry#getAuditApplicationByKey(String) audit applications}. depending on - * the data extraction and generation defined in the applications, values (or derived values) may - * be recorded against several audit entries (one per application represented). - *

- * The return values reflect what was actually persisted and is controlled by the data extractors - * defined in the audit configuration. - *

- * A new read-write transaction is started if there are values to write that there is not a viable - * transaction present. - * - * @param rootPath a base path of {@link AuditPath} key entries concatenated with the path separator - * '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR}) - * @param values the values to audit mapped by {@link AuditPath} key relative to root path - * (may be null) - * @return Returns the values that were actually persisted, keyed by their full path. - * @throws IllegalStateException if the transaction state could not be determined - * - * @since 3.2 - */ - Map recordAuditValues(String rootPath, Map values); - - /** - * The same as {@link AuditComponent#recordAuditValues(String, Map)}, but with controlled usage of userFilter - * - * @param rootPath a base path of {@link AuditPath} key entries concatenated with the path separator - * '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR}) - * @param values the values to audit mapped by {@link AuditPath} key relative to root path - * (may be null) - * @param useUserFilter if false the user filter is disabled. - * @return Returns the values that were actually persisted, keyed by their full path. - * @throws IllegalStateException if the transaction state could not be determined - */ - Map recordAuditValuesWithUserFilter(String rootPath, Map values, boolean useUserFilter); - - /** - * Find audit entries using the given parameters - * - * @param callback the data callback per entry - * @param parameters the parameters for the query (may not be null) - * @param maxResults the maximum number of results to retrieve (must be greater than 0) - * - * @throws IllegalArgumentException if maxResults less or equal to zero - * - * @since 3.2 - */ - void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults); - - /** - * Issue an audit query to retrieve min / max audit record id for a given application. - * - * @param applicationName the name of the application - * @param extremes a list containing min/max or both - * @return a map containing min/max and the associated value - */ - HashMap getAuditMinMaxByApp(String applicationName, List extremes); - - /** - * Issue an audit query to retrieve count of records for a given application. - * - * @param applicationName the name of the application - * @return a map containing min/max and the associated value - */ - default int getAuditEntriesCountByApp(String applicationName) - { - return -1; - } - - /** - * Issue an audit query to retrieve count of records for a given application and properties - * - * @param parameters audit parameters provided by the where clause on the ReST API - * @return a map containing min/max and the associated value - */ - default int getAuditEntriesCountByAppAndProperties(AuditQueryParameters parameters) - { - return -1; - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.audit; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.audit.model.AuditApplication; +import org.alfresco.repo.audit.model.AuditModelRegistry; +import org.alfresco.repo.audit.model._3.AuditPath; +import org.alfresco.service.cmr.audit.AuditQueryParameters; +import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; + +/** + * The audit component. Used by the AuditService and AuditMethodInterceptor to insert audit entries. + * + * @author Derek Hulley + */ +public interface AuditComponent +{ + /** + * Determines whether audit is globally enabled or disabled. + * + * @return Returns true if audit is enabled + * + * @since 3.3 + */ + public boolean isAuditEnabled(); + + /** + * Switch auditing on or off + * + * @param enable + * true to enable auditing or false to disable + * + * @since 3.4 + */ + public void setAuditEnabled(boolean enable); + + /** + * @param userAuditFilter + * UserAuditFilter + * + * @since 4.2 + */ + public void setUserAuditFilter(UserAuditFilter userAuditFilter); + + /** + * Get all registered audit applications, whether active or not. + * + * @return Returns a map of registered audit applications keyed by name + * + * @since 3.4 + */ + public Map getAuditApplications(); + + /** + * Determine whether the audit infrastructure expects audit values to be passed in. This is a helper method to allow optimizations in the client code. Reasons why this method might return false are: auditing is disabled; no audit applications have been registered. Sometimes, depending on the log level, this method may always return true. + * + * + * @return Returns true if the calling code (data producers) should go ahead and generate the data for {@link #recordAuditValues(String, Map) recording}. + * + * @since 3.3 + */ + public boolean areAuditValuesRequired(); + + /** + * Determines if audit values are required based on whether there are any audit applications registered to record data for the given path. This helper method gives data producers a shortcut in the event that nothing would be recorded in any event. + * + * @param path + * the audit path + * @return Returns true if there is at least one audit application registered to handle the given path. + * + * @since 3.4 + */ + public boolean areAuditValuesRequired(String path); + + /** + * Delete audit entries for the given application and time range + * + * @param applicationName + * the name of the application + * @param fromTime + * the start time of entries to remove (inclusive and optional) + * @param toTime + * the end time of entries to remove (exclusive and optional) + * @return Returns the number of entries deleted + * + * @since 3.2 + */ + int deleteAuditEntries(String applicationName, Long fromTime, Long toTime); + + /** + * Delete audit entries for the given application and id range + * + * @param applicationName + * the name of the application + * @param fromId + * the start time of entries to remove (inclusive and optional) + * @param toId + * the end time of entries to remove (exclusive and optional) + * @return Returns the number of entries deleted + * + * @since 5.2.2 + */ + int deleteAuditEntriesByIdRange(String applicationName, Long fromId, Long toId); + + /** + * Delete a discrete list of audit entries based on ID + * + * @param auditEntryIds + * the audit entry IDs to delete + * @return Returns the number of entries deleted + */ + int deleteAuditEntries(List auditEntryIds); + + /** + * Check if an audit path is enabled. The path will be disabled if it or any higher path has been explicitly disabled. Any disabled path will not be processed when data is audited. + * + * @param applicationName + * the name of the application being logged to + * @param path + * the audit path to check or null to assume the application's root path + * @return Returns true if the audit path has been disabled + * + * @since 3.2 + */ + boolean isAuditPathEnabled(String applicationName, String path); + + /** + * Enable auditing (if it is not already enabled) for all paths that contain the given path. The path is the path as originally logged and not the path that the generated data may contain - although this would be similarly enabled. + *

+ * If the enabled + * + * @param applicationName + * the name of the application being logged to + * @param path + * the audit path to check or null to assume the application's root path + * + * @since 3.2 + */ + void enableAudit(String applicationName, String path); + + /** + * Disable auditing (if it is not already disabled) for all paths that contain the given path. The path is the path as originally logged and not the path that the generated data may contain - although this would be similarly disabled. + *

+ * If the path is /x/y then any data paths that start with /x/y will be stripped out before data generators and data recorders are applied. If the path represents the root path of the application, then auditing for that application is effectively disabled. + * + * @param applicationName + * the name of the application being logged to + * @param path + * the audit path to check or null to assume the application's root path + * + * @since 3.2 + */ + void disableAudit(String applicationName, String path); + + /** + * Remove all disabled paths i.e. enable all per-path based auditing. Auditing may still be disabled globally. This is primarily for test purposes; applications should know which paths need {@link #enableAudit(String, String) enabling} or {@link #disableAudit(String, String) disabled}. + * + * @param applicationName + * the name of the application + * + * @since 3.2 + */ + void resetDisabledPaths(String applicationName); + + /** + * Create an audit entry for the given map of values. The map key is a path - starting with '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR}) - relative to the root path provided. + *

+ * The root path and value keys are combined to produce a map of data keyed by full path. This fully-pathed map is then passed through the {@link AuditModelRegistry#getAuditPathMapper() audit path mapper}. The result may yield data destined for several different {@link AuditModelRegistry#getAuditApplicationByKey(String) audit applications}. depending on the data extraction and generation defined in the applications, values (or derived values) may be recorded against several audit entries (one per application represented). + *

+ * The return values reflect what was actually persisted and is controlled by the data extractors defined in the audit configuration. + *

+ * A new read-write transaction is started if there are values to write that there is not a viable transaction present. + * + * @param rootPath + * a base path of {@link AuditPath} key entries concatenated with the path separator '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR}) + * @param values + * the values to audit mapped by {@link AuditPath} key relative to root path (may be null) + * @return Returns the values that were actually persisted, keyed by their full path. + * @throws IllegalStateException + * if the transaction state could not be determined + * + * @since 3.2 + */ + Map recordAuditValues(String rootPath, Map values); + + /** + * The same as {@link AuditComponent#recordAuditValues(String, Map)}, but with controlled usage of userFilter + * + * @param rootPath + * a base path of {@link AuditPath} key entries concatenated with the path separator '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR}) + * @param values + * the values to audit mapped by {@link AuditPath} key relative to root path (may be null) + * @param useUserFilter + * if false the user filter is disabled. + * @return Returns the values that were actually persisted, keyed by their full path. + * @throws IllegalStateException + * if the transaction state could not be determined + */ + Map recordAuditValuesWithUserFilter(String rootPath, Map values, boolean useUserFilter); + + /** + * Find audit entries using the given parameters + * + * @param callback + * the data callback per entry + * @param parameters + * the parameters for the query (may not be null) + * @param maxResults + * the maximum number of results to retrieve (must be greater than 0) + * + * @throws IllegalArgumentException + * if maxResults less or equal to zero + * + * @since 3.2 + */ + void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults); + + /** + * Issue an audit query to retrieve min / max audit record id for a given application. + * + * @param applicationName + * the name of the application + * @param extremes + * a list containing min/max or both + * @return a map containing min/max and the associated value + */ + HashMap getAuditMinMaxByApp(String applicationName, List extremes); + + /** + * Issue an audit query to retrieve count of records for a given application. + * + * @param applicationName + * the name of the application + * @return a map containing min/max and the associated value + */ + default int getAuditEntriesCountByApp(String applicationName) + { + return -1; + } + + /** + * Issue an audit query to retrieve count of records for a given application and properties + * + * @param applicationName + * the name of the application + * @param parameters + * audit parameters provided by the where clause on the ReST API + * @return a map containing min/max and the associated value + */ + default int getAuditEntriesCountByAppAndProperties(String applicationName, AuditQueryParameters parameters) + { + return -1; + + } +} diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java b/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java index 98479db3af1..0ba72169670 100644 --- a/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java @@ -1,963 +1,981 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.repo.audit; - -import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.audit.extractor.DataExtractor; -import org.alfresco.repo.audit.generator.DataGenerator; -import org.alfresco.repo.audit.model.AuditApplication; -import org.alfresco.repo.audit.model.AuditModelRegistry; -import org.alfresco.repo.audit.model.AuditModelRegistryImpl; -import org.alfresco.repo.audit.model.AuditApplication.DataExtractorDefinition; -import org.alfresco.repo.domain.audit.AuditDAO; -import org.alfresco.repo.domain.propval.PropertyValueDAO; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.audit.AuditQueryParameters; -import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.PathMapper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.ParameterCheck; - -/** - * Component that records audit values as well as providing the query implementation. - *

- * To turn on logging of all potentially auditable data, turn on logging for:
- * {@link #INBOUND_LOGGER org.alfresco.repo.audit.inbound}. - *

- * TODO: Respect audit internal - at the moment audit internal is fixed to false. - * - * @author Derek Hulley - * @since 3.2 (in its current form) - */ -public class AuditComponentImpl implements AuditComponent -{ - private static final String INBOUND_LOGGER = "org.alfresco.repo.audit.inbound"; - - private static Log logger = LogFactory.getLog(AuditComponentImpl.class); - private static Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER); - - private AuditModelRegistryImpl auditModelRegistry; - private PropertyValueDAO propertyValueDAO; - private AuditDAO auditDAO; - private TransactionService transactionService; - private AuditFilter auditFilter; - private UserAuditFilter userAuditFilter; - - /** - * Default constructor - */ - public AuditComponentImpl() - { - } - - /** - * Set the registry holding the audit models - * @since 3.2 - */ - public void setAuditModelRegistry(AuditModelRegistryImpl auditModelRegistry) - { - this.auditModelRegistry = auditModelRegistry; - } - - /** - * Set the DAO for manipulating property values - * @since 3.2 - */ - public void setPropertyValueDAO(PropertyValueDAO propertyValueDAO) - { - this.propertyValueDAO = propertyValueDAO; - } - - /** - * Set the DAO for accessing audit data - * @since 3.2 - */ - public void setAuditDAO(AuditDAO auditDAO) - { - this.auditDAO = auditDAO; - } - - /** - * Set the service used to start new transactions - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - /** - * Set the component used to filter which audit events to record - */ - public void setAuditFilter(AuditFilter auditFilter) - { - this.auditFilter = auditFilter; - } - - public void setUserAuditFilter(UserAuditFilter userAuditFilter) - { - this.userAuditFilter = userAuditFilter; - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public int deleteAuditEntries(String applicationName, Long fromTime, Long toTime) - { - Long applicationId = getApplicationId(applicationName); - if (applicationId == null) - { - return 0; - } - - int deleted = auditDAO.deleteAuditEntries(applicationId, fromTime, toTime); - // Done - if (logger.isDebugEnabled()) - { - logger.debug( - "Delete audit " + deleted + " entries for " + applicationName + - " (" + fromTime + " to " + toTime); - } - return deleted; - } - - /** - * {@inheritDoc} - * @since 5.2.2 - */ - public int deleteAuditEntriesByIdRange(String applicationName, Long fromId, Long toId) - { - Long applicationId = getApplicationId(applicationName); - if (applicationId == null) - { - return 0; - } - - int deleted = auditDAO.deleteAuditEntriesByIdRange(applicationId, fromId, toId); - // Done - if (logger.isDebugEnabled()) - { - logger.debug( - "Delete audit " + deleted + " entries for " + applicationName + - " (" + fromId + " to " + toId); - } - return deleted; - } - - private Long getApplicationId(String applicationName) - { - ParameterCheck.mandatory("applicationName", applicationName); - AlfrescoTransactionSupport.checkTransactionReadState(true); - - AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); - if (application == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("No audit application named '" + applicationName + "' has been registered."); - } - return null; - } - - return application.getApplicationId(); - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - @Override - public int deleteAuditEntries(List auditEntryIds) - { - // Shortcut, if necessary - if (auditEntryIds.size() == 0) - { - return 0; - } - return auditDAO.deleteAuditEntries(auditEntryIds); - } - - /** - * @param application the audit application object - * @return Returns a copy of the set of disabled paths associated with the application - */ - @SuppressWarnings("unchecked") - private Set getDisabledPaths(AuditApplication application) - { - try - { - Long disabledPathsId = application.getDisabledPathsId(); - Set disabledPaths = (Set) propertyValueDAO.getPropertyById(disabledPathsId); - return new HashSet(disabledPaths); - } - catch (Throwable e) - { - // Might be an invalid ID, somehow - auditModelRegistry.loadAuditModels(); - throw new AlfrescoRuntimeException("Unabled to get AuditApplication disabled paths: " + application, e); - } - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public boolean isAuditEnabled() - { - return auditModelRegistry.isAuditEnabled(); - } - - /** - * {@inheritDoc} - * @since 3.4 - */ - @Override - public void setAuditEnabled(boolean enable) - { - boolean alreadyEnabled = auditModelRegistry.isAuditEnabled(); - if (alreadyEnabled != enable) - { - // It is changing - auditModelRegistry.stop(); - auditModelRegistry.setProperty( - AuditModelRegistry.AUDIT_PROPERTY_AUDIT_ENABLED, - Boolean.toString(enable).toLowerCase()); - auditModelRegistry.start(); - } - } - - /** - * {@inheritDoc} - * @since 3.4 - */ - public Map getAuditApplications() - { - return auditModelRegistry.getAuditApplications(); - } - - /** - * {@inheritDoc} - *

- * Note that if DEBUG is on for the the {@link #INBOUND_LOGGER}, then true - * will always be returned. - * - * @since 3.2 - */ - public boolean areAuditValuesRequired() - { - return (loggerInbound.isDebugEnabled()) || - (isAuditEnabled() && !auditModelRegistry.getAuditPathMapper().isEmpty()); - } - - /** - * {@inheritDoc} - *

- * Note that if DEBUG is on for the the {@link #INBOUND_LOGGER}, then true - * will always be returned. - * - * @since 3.4 - */ - @Override - public boolean areAuditValuesRequired(String path) - { - PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); - Set mappedPaths = pathMapper.getMappedPathsWithPartialMatch(path); - return loggerInbound.isDebugEnabled() || mappedPaths.size() > 0; - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public boolean isAuditPathEnabled(String applicationName, String path) - { - ParameterCheck.mandatory("applicationName", applicationName); - AlfrescoTransactionSupport.checkTransactionReadState(false); - - AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); - if (application == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("No audit application named '" + applicationName + "' has been registered."); - } - return false; - } - // Ensure that the path gets a valid value - if (path == null) - { - path = AuditApplication.AUDIT_PATH_SEPARATOR + application.getApplicationKey(); - } - else - { - // Check the path against the application - application.checkPath(path); - } - - Set disabledPaths = getDisabledPaths(application); - - // Check if there are any entries that match or supercede the given path - String disablingPath = null;; - for (String disabledPath : disabledPaths) - { - if (path.startsWith(disabledPath)) - { - disablingPath = disabledPath; - break; - } - } - // Done - if (logger.isDebugEnabled()) - { - logger.debug( - "Audit path enabled check: \n" + - " Application: " + applicationName + "\n" + - " Path: " + path + "\n" + - " Disabling Path: " + disablingPath); - } - return disablingPath == null; - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public void enableAudit(String applicationName, String path) - { - ParameterCheck.mandatory("applicationName", applicationName); - AlfrescoTransactionSupport.checkTransactionReadState(true); - - AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); - if (application == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("No audit application named '" + applicationName + "' has been registered."); - } - return; - } - // Ensure that the path gets a valid value - if (path == null) - { - path = AuditApplication.AUDIT_PATH_SEPARATOR + application.getApplicationKey(); - } - else - { - // Check the path against the application - application.checkPath(path); - } - - Long disabledPathsId = application.getDisabledPathsId(); - Set disabledPaths = getDisabledPaths(application); - - // Remove any paths that start with the given path - boolean changed = false; - Iterator iterateDisabledPaths = disabledPaths.iterator(); - while (iterateDisabledPaths.hasNext()) - { - String disabledPath = iterateDisabledPaths.next(); - if (disabledPath.startsWith(path)) - { - iterateDisabledPaths.remove(); - changed = true; - } - } - // Persist, if necessary - if (changed) - { - propertyValueDAO.updateProperty(disabledPathsId, (Serializable) disabledPaths); - if (logger.isDebugEnabled()) - { - logger.debug( - "Audit disabled paths updated: \n" + - " Application: " + applicationName + "\n" + - " Disabled: " + disabledPaths); - } - } - // Done - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public void disableAudit(String applicationName, String path) - { - ParameterCheck.mandatory("applicationName", applicationName); - AlfrescoTransactionSupport.checkTransactionReadState(true); - - AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); - if (application == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("No audit application named '" + applicationName + "' has been registered."); - } - return; - } - // Ensure that the path gets a valid value - if (path == null) - { - path = AuditApplication.AUDIT_PATH_SEPARATOR + application.getApplicationKey(); - } - else - { - // Check the path against the application - application.checkPath(path); - } - - Long disabledPathsId = application.getDisabledPathsId(); - Set disabledPaths = getDisabledPaths(application); - - // Shortcut if the disabled paths contain the exact path - if (disabledPaths.contains(path)) - { - if (logger.isDebugEnabled()) - { - logger.debug( - "Audit disable path already present: \n" + - " Path: " + path); - } - return; - } - - // Bring the set up to date by stripping out unwanted paths - Iterator iterateDisabledPaths = disabledPaths.iterator(); - while (iterateDisabledPaths.hasNext()) - { - String disabledPath = iterateDisabledPaths.next(); - if (disabledPath.startsWith(path)) - { - // We will be superceding this - iterateDisabledPaths.remove(); - } - else if (path.startsWith(disabledPath)) - { - // There is already a superceding path - if (logger.isDebugEnabled()) - { - logger.debug( - "Audit disable path superceded: \n" + - " Path: " + path + "\n" + - " Superceded by: " + disabledPath); - } - return; - } - } - // Add our path in - disabledPaths.add(path); - // Upload the new set - propertyValueDAO.updateProperty(disabledPathsId, (Serializable) disabledPaths); - // Done - if (logger.isDebugEnabled()) - { - logger.debug( - "Audit disabled paths updated: \n" + - " Application: " + applicationName + "\n" + - " Disabled: " + disabledPaths); - } - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public void resetDisabledPaths(String applicationName) - { - ParameterCheck.mandatory("applicationName", applicationName); - AlfrescoTransactionSupport.checkTransactionReadState(true); - - AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); - if (application == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("No audit application named '" + applicationName + "' has been registered."); - } - return; - } - Long disabledPathsId = application.getDisabledPathsId(); - propertyValueDAO.updateProperty(disabledPathsId, (Serializable) Collections.emptySet()); - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Removed all disabled paths for application " + applicationName); - } - } - - @Override - public Map recordAuditValues(String rootPath, Map values) - { - return recordAuditValuesWithUserFilter(rootPath, values, true); - } - - @Override - public Map recordAuditValuesWithUserFilter(String rootPath, Map values, boolean useUserFilter) - { - ParameterCheck.mandatory("rootPath", rootPath); - AuditApplication.checkPathFormat(rootPath); - - String username = AuthenticationUtil.getFullyAuthenticatedUser(); - if (values == null || values.isEmpty() || !areAuditValuesRequired() - || !(userAuditFilter.acceptUser(username) || !useUserFilter) || !auditFilter.accept(rootPath, values)) - { - return Collections.emptyMap(); - } - - // Log inbound values - if (loggerInbound.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(values.size()*64); - sb.append("\n") - .append("Inbound audit values:"); - for (Map.Entry entry : values.entrySet()) - { - String pathElement = entry.getKey(); - String path = AuditApplication.buildPath(rootPath, pathElement); - Serializable value = entry.getValue(); - sb.append("\n\t").append(path).append("=").append(value); - } - loggerInbound.debug(sb.toString()); - } - - // Build the key paths using the session root path - Map pathedValues = new HashMap(values.size() * 2); - for (Map.Entry entry : values.entrySet()) - { - String pathElement = entry.getKey(); - String path = AuditApplication.buildPath(rootPath, pathElement); - pathedValues.put(path, entry.getValue()); - } - - // Translate the values map - PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); - final Map mappedValues = pathMapper.convertMap(pathedValues); - if (mappedValues.isEmpty()) - { - return mappedValues; - } - - // We have something to record. Start a transaction, if necessary - TxnReadState txnState = AlfrescoTransactionSupport.getTransactionReadState(); - switch (txnState) - { - case TXN_NONE: - case TXN_READ_ONLY: - // New transaction - RetryingTransactionCallback> callback = - new RetryingTransactionCallback>() - { - public Map execute() throws Throwable - { - return recordAuditValuesImpl(mappedValues); - } - }; - RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); - txnHelper.setForceWritable(true); - return txnHelper.doInTransaction(callback, false, true); - case TXN_READ_WRITE: - return recordAuditValuesImpl(mappedValues); - default: - throw new IllegalStateException("Unknown txn state: " + txnState); - } - } - - /** - * @since 3.2 - */ - public Map recordAuditValuesImpl(Map mappedValues) - { - // Group the values by root path - Map> mappedValuesByRootKey = new HashMap>(); - for (Map.Entry entry : mappedValues.entrySet()) - { - String path = entry.getKey(); - String rootKey = AuditApplication.getRootKey(path); - Map rootKeyMappedValues = mappedValuesByRootKey.get(rootKey); - if (rootKeyMappedValues == null) - { - rootKeyMappedValues = new HashMap(7); - mappedValuesByRootKey.put(rootKey, rootKeyMappedValues); - } - rootKeyMappedValues.put(path, entry.getValue()); - } - - Map allAuditedValues = new HashMap(mappedValues.size()*2+1); - // Now audit for each of the root keys - for (Map.Entry> entry : mappedValuesByRootKey.entrySet()) - { - String rootKey = entry.getKey(); - Map rootKeyMappedValues = entry.getValue(); - // Get the application - AuditApplication application = auditModelRegistry.getAuditApplicationByKey(rootKey); - if (application == null) - { - // There is no application that uses the root key - logger.debug( - "There is no application for root key: " + rootKey); - continue; - } - // Get the disabled paths - Set disabledPaths = getDisabledPaths(application); - // Do a quick elimination if the root path is disabled - if (disabledPaths.contains(AuditApplication.buildPath(rootKey))) - { - // The root key has been disabled for this application - if (logger.isDebugEnabled()) - { - logger.debug( - "Audit values root path has been excluded by disabled paths: \n" + - " Application: " + application + "\n" + - " Root Path: " + AuditApplication.buildPath(rootKey)); - } - continue; - } - // Do the audit - Map rootKeyAuditValues = audit(application, disabledPaths, rootKeyMappedValues); - allAuditedValues.putAll(rootKeyAuditValues); - } - // Done - return allAuditedValues; - } - - /** - * Audit values for a given application. No path checking is done. - * - * @param application the audit application to audit to - * @param disabledPaths the application's disabled paths - * @param values the values to store keyed by full paths. - * @return Returns all values as audited - */ - private Map audit( - final AuditApplication application, - Set disabledPaths, - final Map values) - { - // Get the model ID for the application - Long applicationId = application.getApplicationId(); - if (applicationId == null) - { - throw new AuditException("No persisted instance exists for audit application: " + application); - } - - // Check if there is anything to audit - if (values.size() == 0) - { - if (logger.isDebugEnabled()) - { - logger.debug( - "Audit values have all been excluded by disabled paths: \n" + - " Application: " + application + "\n" + - " Values: " + values); - } - return Collections.emptyMap(); - } - - Set generatorKeys = values.keySet(); - // Eliminate any paths that have been disabled - Iterator generatorKeysIterator = generatorKeys.iterator(); - while(generatorKeysIterator.hasNext()) - { - String generatorKey = generatorKeysIterator.next(); - for (String disabledPath : disabledPaths) - { - if (generatorKey.startsWith(disabledPath)) - { - // The pathed value is excluded - generatorKeysIterator.remove(); - } - } - } - - // Generate data - Map generators = application.getDataGenerators(generatorKeys); - Map auditData = generateData(generators); - - // Now extract values - Map extractedData = AuthenticationUtil.runAs(new RunAsWork>() - { - public Map doWork() throws Exception - { - return extractData(application, values); - } - }, AuthenticationUtil.getSystemUserName()); - - // Combine extracted and generated values (extracted data takes precedence) - auditData.putAll(extractedData); - - // Filter data - // See MNT-14136, MNT-8401 - for (Map.Entry value : auditData.entrySet()) - { - String root = value.getKey(); - int index = root.lastIndexOf("/"); - Map argc = new HashMap(1); - argc.put(root.substring(index, root.length()).substring(1), value.getValue()); - if (!auditFilter.accept(root.substring(0, index), argc)) - { - return Collections.emptyMap(); - } - } - - // Time and username are intrinsic - long time = System.currentTimeMillis(); - String username = AuthenticationUtil.getFullyAuthenticatedUser(); - - Long entryId = null; - if (!auditData.isEmpty()) - { - // Persist the values (if not just gathering data in a pre call for use in a post call) - boolean justGatherPreCallData = application.isApplicationJustGeneratingPreCallData(); - if (!justGatherPreCallData) - { - entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData); - } - // Done - if (logger.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(); - sb.append( - ((justGatherPreCallData) ? "\nPreCallData: \n" : "\nNew audit entry: \n") + - "\tApplication ID: " + applicationId + "\n" + - ((justGatherPreCallData) ? "" : "\tEntry ID: " + entryId + "\n") + - "\tValues: " + "\n"); - for (Map.Entry entry : values.entrySet()) - { - sb.append("\t\t").append(entry).append("\n"); - } - sb.append("\n\tAudit Data: \n"); - for (Map.Entry entry : auditData.entrySet()) - { - sb.append("\t\t").append(entry).append("\n"); - } - logger.debug(sb.toString()); - } - } - else - { - // Done ... nothing - if (logger.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(); - sb.append( - "\nNothing audited: \n" + - "\tApplication ID: " + applicationId + "\n" + - "\tEntry ID: " + entryId + "\n" + - "\tValues: " + "\n"); - for (Map.Entry entry : values.entrySet()) - { - sb.append("\t\t").append(entry).append("\n"); - } - logger.debug(sb.toString()); - } - } - - return auditData; - } - - /** - * Extracts data from a given map using data extractors from the given application. - * - * @param application the application providing the data extractors - * @param values the data values from which to generate data - * @return Returns a map of derived data keyed by full path - * - * @since 3.2 - */ - private Map extractData( - AuditApplication application, - Map values) - { - Map newData = new HashMap(values.size()); - - List extractors = application.getDataExtractors(); - for (DataExtractorDefinition extractorDef : extractors) - { - DataExtractor extractor = extractorDef.getDataExtractor(); - String triggerPath = extractorDef.getDataTrigger(); - String sourcePath = extractorDef.getDataSource(); - String targetPath = extractorDef.getDataTarget(); - - // Check if it is triggered - if (!values.containsKey(triggerPath)) - { - continue; // It is not triggered - } - - // We observe the key, not the actual value - if (!values.containsKey(sourcePath)) - { - continue; // There is no data to extract - } - - Serializable value = values.get(sourcePath); - - // Check if the extraction is supported - if (!extractor.isSupported(value)) - { - continue; - } - // Use the extractor to pull the value out - final Serializable data; - try - { - data = extractor.extractData(value); - } - catch (Throwable e) - { - throw new AlfrescoRuntimeException( - "Failed to extract audit data: \n" + - " Path: " + sourcePath + "\n" + - " Raw value: " + value + "\n" + - " Extractor: " + extractor, - e); - } - // Add it to the map - newData.put(targetPath, data); - } - // Done - if (logger.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(); - sb.append( - "\nExtracted audit data: \n" + - "\tApplication: " + application + "\n" + - "\tValues: " + "\n"); - for (Map.Entry entry : values.entrySet()) - { - sb.append("\t\t").append(entry).append("\n"); - } - sb.append("\n\tNew Data: \n"); - for (Map.Entry entry : newData.entrySet()) - { - sb.append("\t\t").append(entry).append("\n"); - } - logger.debug(sb.toString()); - } - return newData; - } - - /** - * @param generators the data generators - * @return Returns a map of generated data keyed by full path - * - * @since 3.2 - */ - private Map generateData(Map generators) - { - Map newData = new HashMap(generators.size() + 5); - for (Map.Entry entry : generators.entrySet()) - { - String path = entry.getKey(); - DataGenerator generator = entry.getValue(); - final Serializable data; - try - { - data = generator.getData(); - } - catch (Throwable e) - { - throw new AlfrescoRuntimeException( - "Failed to generate audit data: \n" + - " Path: " + path + "\n" + - " Generator: " + generator, - e); - } - // Add it to the map - newData.put(path, data); - } - // Done - return newData; - } - - /** - * {@inheritDoc} - */ - public void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults) - { - ParameterCheck.mandatory("callback", callback); - ParameterCheck.mandatory("parameters", parameters); - - // Shortcuts - if (parameters.isZeroResultQuery()) - { - return; - } - - auditDAO.findAuditEntries(callback, parameters, maxResults); - } - - /** - * {@inheritDoc} - */ - public HashMap getAuditMinMaxByApp(String applicationName, List extremes) - { - // Get the id for the application - AuditApplication app = auditModelRegistry.getAuditApplicationByName(applicationName); - Long applicationId = app.getApplicationId(); - if (applicationId == null) - { - throw new AuditException("No persisted instance exists for audit application: " + app); - } - - return auditDAO.getAuditMinMaxByApp(applicationId, extremes); - } - - @Override - public int getAuditEntriesCountByApp(String applicationName) - { - // Get the id for the application - AuditApplication app = auditModelRegistry.getAuditApplicationByName(applicationName); - Long applicationId = app.getApplicationId(); - if (applicationId == null) - { - throw new AuditException("No persisted instance exists for audit application: " + app); - } - - return auditDAO.getAuditEntriesCountByApp(applicationId); - } - - @Override public int getAuditEntriesCountByAppAndProperties(AuditQueryParameters parameters) - { - return auditDAO.getAuditEntriesCountByAppAndProperties(parameters); - } -} \ No newline at end of file +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.audit; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.ParameterCheck; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.audit.extractor.DataExtractor; +import org.alfresco.repo.audit.generator.DataGenerator; +import org.alfresco.repo.audit.model.AuditApplication; +import org.alfresco.repo.audit.model.AuditApplication.DataExtractorDefinition; +import org.alfresco.repo.audit.model.AuditModelRegistry; +import org.alfresco.repo.audit.model.AuditModelRegistryImpl; +import org.alfresco.repo.domain.audit.AuditDAO; +import org.alfresco.repo.domain.propval.PropertyValueDAO; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.audit.AuditQueryParameters; +import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PathMapper; + +/** + * Component that records audit values as well as providing the query implementation. + *

+ * To turn on logging of all potentially auditable data, turn on logging for:
+ * {@link #INBOUND_LOGGER org.alfresco.repo.audit.inbound}. + *

+ * TODO: Respect audit internal - at the moment audit internal is fixed to false. + * + * @author Derek Hulley + * @since 3.2 (in its current form) + */ +public class AuditComponentImpl implements AuditComponent +{ + private static final String INBOUND_LOGGER = "org.alfresco.repo.audit.inbound"; + + private static Log logger = LogFactory.getLog(AuditComponentImpl.class); + private static Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER); + + private AuditModelRegistryImpl auditModelRegistry; + private PropertyValueDAO propertyValueDAO; + private AuditDAO auditDAO; + private TransactionService transactionService; + private AuditFilter auditFilter; + private UserAuditFilter userAuditFilter; + + /** + * Default constructor + */ + public AuditComponentImpl() + {} + + /** + * Set the registry holding the audit models + * + * @since 3.2 + */ + public void setAuditModelRegistry(AuditModelRegistryImpl auditModelRegistry) + { + this.auditModelRegistry = auditModelRegistry; + } + + /** + * Set the DAO for manipulating property values + * + * @since 3.2 + */ + public void setPropertyValueDAO(PropertyValueDAO propertyValueDAO) + { + this.propertyValueDAO = propertyValueDAO; + } + + /** + * Set the DAO for accessing audit data + * + * @since 3.2 + */ + public void setAuditDAO(AuditDAO auditDAO) + { + this.auditDAO = auditDAO; + } + + /** + * Set the service used to start new transactions + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Set the component used to filter which audit events to record + */ + public void setAuditFilter(AuditFilter auditFilter) + { + this.auditFilter = auditFilter; + } + + public void setUserAuditFilter(UserAuditFilter userAuditFilter) + { + this.userAuditFilter = userAuditFilter; + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public int deleteAuditEntries(String applicationName, Long fromTime, Long toTime) + { + Long applicationId = getApplicationId(applicationName); + if (applicationId == null) + { + return 0; + } + + int deleted = auditDAO.deleteAuditEntries(applicationId, fromTime, toTime); + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Delete audit " + deleted + " entries for " + applicationName + + " (" + fromTime + " to " + toTime); + } + return deleted; + } + + /** + * {@inheritDoc} + * + * @since 5.2.2 + */ + public int deleteAuditEntriesByIdRange(String applicationName, Long fromId, Long toId) + { + Long applicationId = getApplicationId(applicationName); + if (applicationId == null) + { + return 0; + } + + int deleted = auditDAO.deleteAuditEntriesByIdRange(applicationId, fromId, toId); + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Delete audit " + deleted + " entries for " + applicationName + + " (" + fromId + " to " + toId); + } + return deleted; + } + + private Long getApplicationId(String applicationName) + { + ParameterCheck.mandatory("applicationName", applicationName); + AlfrescoTransactionSupport.checkTransactionReadState(true); + + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return null; + } + + return application.getApplicationId(); + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + @Override + public int deleteAuditEntries(List auditEntryIds) + { + // Shortcut, if necessary + if (auditEntryIds.size() == 0) + { + return 0; + } + return auditDAO.deleteAuditEntries(auditEntryIds); + } + + /** + * @param application + * the audit application object + * @return Returns a copy of the set of disabled paths associated with the application + */ + @SuppressWarnings("unchecked") + private Set getDisabledPaths(AuditApplication application) + { + try + { + Long disabledPathsId = application.getDisabledPathsId(); + Set disabledPaths = (Set) propertyValueDAO.getPropertyById(disabledPathsId); + return new HashSet(disabledPaths); + } + catch (Throwable e) + { + // Might be an invalid ID, somehow + auditModelRegistry.loadAuditModels(); + throw new AlfrescoRuntimeException("Unabled to get AuditApplication disabled paths: " + application, e); + } + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public boolean isAuditEnabled() + { + return auditModelRegistry.isAuditEnabled(); + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + @Override + public void setAuditEnabled(boolean enable) + { + boolean alreadyEnabled = auditModelRegistry.isAuditEnabled(); + if (alreadyEnabled != enable) + { + // It is changing + auditModelRegistry.stop(); + auditModelRegistry.setProperty( + AuditModelRegistry.AUDIT_PROPERTY_AUDIT_ENABLED, + Boolean.toString(enable).toLowerCase()); + auditModelRegistry.start(); + } + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + public Map getAuditApplications() + { + return auditModelRegistry.getAuditApplications(); + } + + /** + * {@inheritDoc} + *

+ * Note that if DEBUG is on for the the {@link #INBOUND_LOGGER}, then true will always be returned. + * + * @since 3.2 + */ + public boolean areAuditValuesRequired() + { + return (loggerInbound.isDebugEnabled()) || + (isAuditEnabled() && !auditModelRegistry.getAuditPathMapper().isEmpty()); + } + + /** + * {@inheritDoc} + *

+ * Note that if DEBUG is on for the the {@link #INBOUND_LOGGER}, then true will always be returned. + * + * @since 3.4 + */ + @Override + public boolean areAuditValuesRequired(String path) + { + PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); + Set mappedPaths = pathMapper.getMappedPathsWithPartialMatch(path); + return loggerInbound.isDebugEnabled() || mappedPaths.size() > 0; + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public boolean isAuditPathEnabled(String applicationName, String path) + { + ParameterCheck.mandatory("applicationName", applicationName); + AlfrescoTransactionSupport.checkTransactionReadState(false); + + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return false; + } + // Ensure that the path gets a valid value + if (path == null) + { + path = AuditApplication.AUDIT_PATH_SEPARATOR + application.getApplicationKey(); + } + else + { + // Check the path against the application + application.checkPath(path); + } + + Set disabledPaths = getDisabledPaths(application); + + // Check if there are any entries that match or supercede the given path + String disablingPath = null; + ; + for (String disabledPath : disabledPaths) + { + if (path.startsWith(disabledPath)) + { + disablingPath = disabledPath; + break; + } + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit path enabled check: \n" + + " Application: " + applicationName + "\n" + + " Path: " + path + "\n" + + " Disabling Path: " + disablingPath); + } + return disablingPath == null; + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public void enableAudit(String applicationName, String path) + { + ParameterCheck.mandatory("applicationName", applicationName); + AlfrescoTransactionSupport.checkTransactionReadState(true); + + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return; + } + // Ensure that the path gets a valid value + if (path == null) + { + path = AuditApplication.AUDIT_PATH_SEPARATOR + application.getApplicationKey(); + } + else + { + // Check the path against the application + application.checkPath(path); + } + + Long disabledPathsId = application.getDisabledPathsId(); + Set disabledPaths = getDisabledPaths(application); + + // Remove any paths that start with the given path + boolean changed = false; + Iterator iterateDisabledPaths = disabledPaths.iterator(); + while (iterateDisabledPaths.hasNext()) + { + String disabledPath = iterateDisabledPaths.next(); + if (disabledPath.startsWith(path)) + { + iterateDisabledPaths.remove(); + changed = true; + } + } + // Persist, if necessary + if (changed) + { + propertyValueDAO.updateProperty(disabledPathsId, (Serializable) disabledPaths); + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disabled paths updated: \n" + + " Application: " + applicationName + "\n" + + " Disabled: " + disabledPaths); + } + } + // Done + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public void disableAudit(String applicationName, String path) + { + ParameterCheck.mandatory("applicationName", applicationName); + AlfrescoTransactionSupport.checkTransactionReadState(true); + + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return; + } + // Ensure that the path gets a valid value + if (path == null) + { + path = AuditApplication.AUDIT_PATH_SEPARATOR + application.getApplicationKey(); + } + else + { + // Check the path against the application + application.checkPath(path); + } + + Long disabledPathsId = application.getDisabledPathsId(); + Set disabledPaths = getDisabledPaths(application); + + // Shortcut if the disabled paths contain the exact path + if (disabledPaths.contains(path)) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disable path already present: \n" + + " Path: " + path); + } + return; + } + + // Bring the set up to date by stripping out unwanted paths + Iterator iterateDisabledPaths = disabledPaths.iterator(); + while (iterateDisabledPaths.hasNext()) + { + String disabledPath = iterateDisabledPaths.next(); + if (disabledPath.startsWith(path)) + { + // We will be superceding this + iterateDisabledPaths.remove(); + } + else if (path.startsWith(disabledPath)) + { + // There is already a superceding path + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disable path superceded: \n" + + " Path: " + path + "\n" + + " Superceded by: " + disabledPath); + } + return; + } + } + // Add our path in + disabledPaths.add(path); + // Upload the new set + propertyValueDAO.updateProperty(disabledPathsId, (Serializable) disabledPaths); + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disabled paths updated: \n" + + " Application: " + applicationName + "\n" + + " Disabled: " + disabledPaths); + } + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public void resetDisabledPaths(String applicationName) + { + ParameterCheck.mandatory("applicationName", applicationName); + AlfrescoTransactionSupport.checkTransactionReadState(true); + + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return; + } + Long disabledPathsId = application.getDisabledPathsId(); + propertyValueDAO.updateProperty(disabledPathsId, (Serializable) Collections.emptySet()); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Removed all disabled paths for application " + applicationName); + } + } + + @Override + public Map recordAuditValues(String rootPath, Map values) + { + return recordAuditValuesWithUserFilter(rootPath, values, true); + } + + @Override + public Map recordAuditValuesWithUserFilter(String rootPath, Map values, boolean useUserFilter) + { + ParameterCheck.mandatory("rootPath", rootPath); + AuditApplication.checkPathFormat(rootPath); + + String username = AuthenticationUtil.getFullyAuthenticatedUser(); + if (values == null || values.isEmpty() || !areAuditValuesRequired() + || !(userAuditFilter.acceptUser(username) || !useUserFilter) || !auditFilter.accept(rootPath, values)) + { + return Collections.emptyMap(); + } + + // Log inbound values + if (loggerInbound.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(values.size() * 64); + sb.append("\n") + .append("Inbound audit values:"); + for (Map.Entry entry : values.entrySet()) + { + String pathElement = entry.getKey(); + String path = AuditApplication.buildPath(rootPath, pathElement); + Serializable value = entry.getValue(); + sb.append("\n\t").append(path).append("=").append(value); + } + loggerInbound.debug(sb.toString()); + } + + // Build the key paths using the session root path + Map pathedValues = new HashMap(values.size() * 2); + for (Map.Entry entry : values.entrySet()) + { + String pathElement = entry.getKey(); + String path = AuditApplication.buildPath(rootPath, pathElement); + pathedValues.put(path, entry.getValue()); + } + + // Translate the values map + PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); + final Map mappedValues = pathMapper.convertMap(pathedValues); + if (mappedValues.isEmpty()) + { + return mappedValues; + } + + // We have something to record. Start a transaction, if necessary + TxnReadState txnState = AlfrescoTransactionSupport.getTransactionReadState(); + switch (txnState) + { + case TXN_NONE: + case TXN_READ_ONLY: + // New transaction + RetryingTransactionCallback> callback = new RetryingTransactionCallback>() { + public Map execute() throws Throwable + { + return recordAuditValuesImpl(mappedValues); + } + }; + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setForceWritable(true); + return txnHelper.doInTransaction(callback, false, true); + case TXN_READ_WRITE: + return recordAuditValuesImpl(mappedValues); + default: + throw new IllegalStateException("Unknown txn state: " + txnState); + } + } + + /** + * @since 3.2 + */ + public Map recordAuditValuesImpl(Map mappedValues) + { + // Group the values by root path + Map> mappedValuesByRootKey = new HashMap>(); + for (Map.Entry entry : mappedValues.entrySet()) + { + String path = entry.getKey(); + String rootKey = AuditApplication.getRootKey(path); + Map rootKeyMappedValues = mappedValuesByRootKey.get(rootKey); + if (rootKeyMappedValues == null) + { + rootKeyMappedValues = new HashMap(7); + mappedValuesByRootKey.put(rootKey, rootKeyMappedValues); + } + rootKeyMappedValues.put(path, entry.getValue()); + } + + Map allAuditedValues = new HashMap(mappedValues.size() * 2 + 1); + // Now audit for each of the root keys + for (Map.Entry> entry : mappedValuesByRootKey.entrySet()) + { + String rootKey = entry.getKey(); + Map rootKeyMappedValues = entry.getValue(); + // Get the application + AuditApplication application = auditModelRegistry.getAuditApplicationByKey(rootKey); + if (application == null) + { + // There is no application that uses the root key + logger.debug( + "There is no application for root key: " + rootKey); + continue; + } + // Get the disabled paths + Set disabledPaths = getDisabledPaths(application); + // Do a quick elimination if the root path is disabled + if (disabledPaths.contains(AuditApplication.buildPath(rootKey))) + { + // The root key has been disabled for this application + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit values root path has been excluded by disabled paths: \n" + + " Application: " + application + "\n" + + " Root Path: " + AuditApplication.buildPath(rootKey)); + } + continue; + } + // Do the audit + Map rootKeyAuditValues = audit(application, disabledPaths, rootKeyMappedValues); + allAuditedValues.putAll(rootKeyAuditValues); + } + // Done + return allAuditedValues; + } + + /** + * Audit values for a given application. No path checking is done. + * + * @param application + * the audit application to audit to + * @param disabledPaths + * the application's disabled paths + * @param values + * the values to store keyed by full paths. + * @return Returns all values as audited + */ + private Map audit( + final AuditApplication application, + Set disabledPaths, + final Map values) + { + // Get the model ID for the application + Long applicationId = application.getApplicationId(); + if (applicationId == null) + { + throw new AuditException("No persisted instance exists for audit application: " + application); + } + + // Check if there is anything to audit + if (values.size() == 0) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit values have all been excluded by disabled paths: \n" + + " Application: " + application + "\n" + + " Values: " + values); + } + return Collections.emptyMap(); + } + + Set generatorKeys = values.keySet(); + // Eliminate any paths that have been disabled + Iterator generatorKeysIterator = generatorKeys.iterator(); + while (generatorKeysIterator.hasNext()) + { + String generatorKey = generatorKeysIterator.next(); + for (String disabledPath : disabledPaths) + { + if (generatorKey.startsWith(disabledPath)) + { + // The pathed value is excluded + generatorKeysIterator.remove(); + } + } + } + + // Generate data + Map generators = application.getDataGenerators(generatorKeys); + Map auditData = generateData(generators); + + // Now extract values + Map extractedData = AuthenticationUtil.runAs(new RunAsWork>() { + public Map doWork() throws Exception + { + return extractData(application, values); + } + }, AuthenticationUtil.getSystemUserName()); + + // Combine extracted and generated values (extracted data takes precedence) + auditData.putAll(extractedData); + + // Filter data + // See MNT-14136, MNT-8401 + for (Map.Entry value : auditData.entrySet()) + { + String root = value.getKey(); + int index = root.lastIndexOf("/"); + Map argc = new HashMap(1); + argc.put(root.substring(index, root.length()).substring(1), value.getValue()); + if (!auditFilter.accept(root.substring(0, index), argc)) + { + return Collections.emptyMap(); + } + } + + // Time and username are intrinsic + long time = System.currentTimeMillis(); + String username = AuthenticationUtil.getFullyAuthenticatedUser(); + + Long entryId = null; + if (!auditData.isEmpty()) + { + // Persist the values (if not just gathering data in a pre call for use in a post call) + boolean justGatherPreCallData = application.isApplicationJustGeneratingPreCallData(); + if (!justGatherPreCallData) + { + entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData); + } + // Done + if (logger.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(); + sb.append( + ((justGatherPreCallData) ? "\nPreCallData: \n" : "\nNew audit entry: \n") + + "\tApplication ID: " + applicationId + "\n" + + ((justGatherPreCallData) ? "" : "\tEntry ID: " + entryId + "\n") + + "\tValues: " + "\n"); + for (Map.Entry entry : values.entrySet()) + { + sb.append("\t\t").append(entry).append("\n"); + } + sb.append("\n\tAudit Data: \n"); + for (Map.Entry entry : auditData.entrySet()) + { + sb.append("\t\t").append(entry).append("\n"); + } + logger.debug(sb.toString()); + } + } + else + { + // Done ... nothing + if (logger.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(); + sb.append( + "\nNothing audited: \n" + + "\tApplication ID: " + applicationId + "\n" + + "\tEntry ID: " + entryId + "\n" + + "\tValues: " + "\n"); + for (Map.Entry entry : values.entrySet()) + { + sb.append("\t\t").append(entry).append("\n"); + } + logger.debug(sb.toString()); + } + } + + return auditData; + } + + /** + * Extracts data from a given map using data extractors from the given application. + * + * @param application + * the application providing the data extractors + * @param values + * the data values from which to generate data + * @return Returns a map of derived data keyed by full path + * + * @since 3.2 + */ + private Map extractData( + AuditApplication application, + Map values) + { + Map newData = new HashMap(values.size()); + + List extractors = application.getDataExtractors(); + for (DataExtractorDefinition extractorDef : extractors) + { + DataExtractor extractor = extractorDef.getDataExtractor(); + String triggerPath = extractorDef.getDataTrigger(); + String sourcePath = extractorDef.getDataSource(); + String targetPath = extractorDef.getDataTarget(); + + // Check if it is triggered + if (!values.containsKey(triggerPath)) + { + continue; // It is not triggered + } + + // We observe the key, not the actual value + if (!values.containsKey(sourcePath)) + { + continue; // There is no data to extract + } + + Serializable value = values.get(sourcePath); + + // Check if the extraction is supported + if (!extractor.isSupported(value)) + { + continue; + } + // Use the extractor to pull the value out + final Serializable data; + try + { + data = extractor.extractData(value); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException( + "Failed to extract audit data: \n" + + " Path: " + sourcePath + "\n" + + " Raw value: " + value + "\n" + + " Extractor: " + extractor, + e); + } + // Add it to the map + newData.put(targetPath, data); + } + // Done + if (logger.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(); + sb.append( + "\nExtracted audit data: \n" + + "\tApplication: " + application + "\n" + + "\tValues: " + "\n"); + for (Map.Entry entry : values.entrySet()) + { + sb.append("\t\t").append(entry).append("\n"); + } + sb.append("\n\tNew Data: \n"); + for (Map.Entry entry : newData.entrySet()) + { + sb.append("\t\t").append(entry).append("\n"); + } + logger.debug(sb.toString()); + } + return newData; + } + + /** + * @param generators + * the data generators + * @return Returns a map of generated data keyed by full path + * + * @since 3.2 + */ + private Map generateData(Map generators) + { + Map newData = new HashMap(generators.size() + 5); + for (Map.Entry entry : generators.entrySet()) + { + String path = entry.getKey(); + DataGenerator generator = entry.getValue(); + final Serializable data; + try + { + data = generator.getData(); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException( + "Failed to generate audit data: \n" + + " Path: " + path + "\n" + + " Generator: " + generator, + e); + } + // Add it to the map + newData.put(path, data); + } + // Done + return newData; + } + + /** + * {@inheritDoc} + */ + public void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults) + { + ParameterCheck.mandatory("callback", callback); + ParameterCheck.mandatory("parameters", parameters); + + // Shortcuts + if (parameters.isZeroResultQuery()) + { + return; + } + + auditDAO.findAuditEntries(callback, parameters, maxResults); + } + + /** + * {@inheritDoc} + */ + public HashMap getAuditMinMaxByApp(String applicationName, List extremes) + { + // Get the id for the application + AuditApplication app = auditModelRegistry.getAuditApplicationByName(applicationName); + Long applicationId = app.getApplicationId(); + if (applicationId == null) + { + throw new AuditException("No persisted instance exists for audit application: " + app); + } + + return auditDAO.getAuditMinMaxByApp(applicationId, extremes); + } + + @Override + public int getAuditEntriesCountByApp(String applicationName) + { + // Get the id for the application + AuditApplication app = auditModelRegistry.getAuditApplicationByName(applicationName); + Long applicationId = app.getApplicationId(); + if (applicationId == null) + { + throw new AuditException("No persisted instance exists for audit application: " + app); + } + + return auditDAO.getAuditEntriesCountByApp(applicationId); + } + + @Override + public int getAuditEntriesCountByAppAndProperties(String applicationName, AuditQueryParameters parameters) + { + return auditDAO.getAuditEntriesCountByAppAndProperties(applicationName, parameters); + + } +} diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditServiceImpl.java b/repository/src/main/java/org/alfresco/repo/audit/AuditServiceImpl.java index 4824914e513..7c358194566 100644 --- a/repository/src/main/java/org/alfresco/repo/audit/AuditServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditServiceImpl.java @@ -1,197 +1,210 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.repo.audit; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.alfresco.service.cmr.audit.AuditQueryParameters; -import org.alfresco.service.cmr.audit.AuditService; - -/** - * The implementation of the AuditService for application auditing. - * - * @author Derek Hulley - * @since 3.2 - */ -public class AuditServiceImpl implements AuditService -{ - private AuditComponent auditComponent; - - public AuditServiceImpl() - { - super(); - } - - public void setAuditComponent(AuditComponent auditComponent) - { - this.auditComponent = auditComponent; - } - - /** - * {@inheritDoc} - * @since 3.4 - */ - public boolean isAuditEnabled() - { - return auditComponent.isAuditEnabled(); - } - - /** - * {@inheritDoc} - * @since 3.4 - */ - @Override - public void setAuditEnabled(boolean enable) - { - auditComponent.setAuditEnabled(enable); - } - - /** - * {@inheritDoc} - * @since 3.4 - */ - @Override - public Map getAuditApplications() - { - Map apps = auditComponent.getAuditApplications(); - - Map ret = new TreeMap(); - for (String app : apps.keySet()) - { - String name = app; - String key = org.alfresco.repo.audit.model.AuditApplication.AUDIT_PATH_SEPARATOR + apps.get(app).getApplicationKey(); - boolean enabled = auditComponent.isAuditPathEnabled(app, key); - AuditApplication auditApplication = new AuditApplication(name, key, enabled); - ret.put(name, auditApplication); - } - return ret; - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public boolean isAuditEnabled(String applicationName, String path) - { - return auditComponent.isAuditPathEnabled(applicationName, path); - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public void enableAudit(String applicationName, String path) - { - auditComponent.enableAudit(applicationName, path); - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public void disableAudit(String applicationName, String path) - { - auditComponent.disableAudit(applicationName, path); - } - - /** - * {@inheritDoc} - * @since 3.2 - */ - public int clearAudit(String applicationName) - { - Long now = Long.valueOf(System.currentTimeMillis()); - return auditComponent.deleteAuditEntries(applicationName, null, now); - } - - /** - * {@inheritDoc} - * @since 3.4 - */ - @Override - public int clearAudit(String applicationName, Long fromTime, Long toTime) - { - toTime = (toTime == null) ? Long.valueOf(System.currentTimeMillis()) : toTime; - return auditComponent.deleteAuditEntries(applicationName, fromTime, toTime); - } - - /** - * {@inheritDoc} - * @since 5.2.2 - */ - @Override - public int clearAuditByIdRange(String applicationName, Long fromId, Long toId) - { - return auditComponent.deleteAuditEntriesByIdRange(applicationName, fromId, toId); - } - - /** - * {@inheritDoc} - * @since 3.4 - */ - @Override - public int clearAudit(List auditEntryIds) - { - return auditComponent.deleteAuditEntries(auditEntryIds); - } - - /** - * {@inheritDoc} - * @since 3.3 - */ - public void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults) - { - auditComponent.auditQuery(callback, parameters, maxResults); - } - - /** - * {@inheritDoc} - */ - public HashMap getAuditMinMaxByApp(String applicationName, List extremes) - { - return auditComponent.getAuditMinMaxByApp(applicationName, extremes); - } - - /** - * {@inheritDoc} - */ - @Override - public int getAuditEntriesCountByApp(String applicationName) - { - return auditComponent.getAuditEntriesCountByApp(applicationName); - } - - /** - * {@inheritDoc} - */ - @Override public int getAuditEntriesCountByAppAndProperties(AuditQueryParameters parameters) - { - return auditComponent.getAuditEntriesCountByAppAndProperties(parameters); - } -} \ No newline at end of file +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.audit; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.alfresco.service.cmr.audit.AuditQueryParameters; +import org.alfresco.service.cmr.audit.AuditService; + +/** + * The implementation of the AuditService for application auditing. + * + * @author Derek Hulley + * @since 3.2 + */ +public class AuditServiceImpl implements AuditService +{ + private AuditComponent auditComponent; + + public AuditServiceImpl() + { + super(); + } + + public void setAuditComponent(AuditComponent auditComponent) + { + this.auditComponent = auditComponent; + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + public boolean isAuditEnabled() + { + return auditComponent.isAuditEnabled(); + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + @Override + public void setAuditEnabled(boolean enable) + { + auditComponent.setAuditEnabled(enable); + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + @Override + public Map getAuditApplications() + { + Map apps = auditComponent.getAuditApplications(); + + Map ret = new TreeMap(); + for (String app : apps.keySet()) + { + String name = app; + String key = org.alfresco.repo.audit.model.AuditApplication.AUDIT_PATH_SEPARATOR + apps.get(app).getApplicationKey(); + boolean enabled = auditComponent.isAuditPathEnabled(app, key); + AuditApplication auditApplication = new AuditApplication(name, key, enabled); + ret.put(name, auditApplication); + } + return ret; + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public boolean isAuditEnabled(String applicationName, String path) + { + return auditComponent.isAuditPathEnabled(applicationName, path); + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public void enableAudit(String applicationName, String path) + { + auditComponent.enableAudit(applicationName, path); + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public void disableAudit(String applicationName, String path) + { + auditComponent.disableAudit(applicationName, path); + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public int clearAudit(String applicationName) + { + Long now = Long.valueOf(System.currentTimeMillis()); + return auditComponent.deleteAuditEntries(applicationName, null, now); + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + @Override + public int clearAudit(String applicationName, Long fromTime, Long toTime) + { + toTime = (toTime == null) ? Long.valueOf(System.currentTimeMillis()) : toTime; + return auditComponent.deleteAuditEntries(applicationName, fromTime, toTime); + } + + /** + * {@inheritDoc} + * + * @since 5.2.2 + */ + @Override + public int clearAuditByIdRange(String applicationName, Long fromId, Long toId) + { + return auditComponent.deleteAuditEntriesByIdRange(applicationName, fromId, toId); + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + @Override + public int clearAudit(List auditEntryIds) + { + return auditComponent.deleteAuditEntries(auditEntryIds); + } + + /** + * {@inheritDoc} + * + * @since 3.3 + */ + public void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults) + { + auditComponent.auditQuery(callback, parameters, maxResults); + } + + /** + * {@inheritDoc} + */ + public HashMap getAuditMinMaxByApp(String applicationName, List extremes) + { + return auditComponent.getAuditMinMaxByApp(applicationName, extremes); + } + + /** + * {@inheritDoc} + */ + @Override + public int getAuditEntriesCountByApp(String applicationName) + { + return auditComponent.getAuditEntriesCountByApp(applicationName); + } + + /** + * {@inheritDoc} + */ + @Override + public int getAuditEntriesCountByAppAndProperties(String applicationName, AuditQueryParameters parameters) + { + return auditComponent.getAuditEntriesCountByAppAndProperties(applicationName, parameters); + + } +} diff --git a/repository/src/main/java/org/alfresco/repo/domain/CrcHelper.java b/repository/src/main/java/org/alfresco/repo/domain/CrcHelper.java index d3cdb157b6f..c26ea6e3fb2 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/CrcHelper.java +++ b/repository/src/main/java/org/alfresco/repo/domain/CrcHelper.java @@ -1,123 +1,117 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ - -package org.alfresco.repo.domain; - -import java.io.UnsupportedEncodingException; -import java.util.zip.CRC32; - -import org.alfresco.util.Pair; - -/** - * Helper class to calculate CRC values for string persistence. - * - * @author Derek Hulley - * @since 3.2 - */ -public class CrcHelper -{ - public static final String EMPTY_STRING = ".empty"; - - /** - * Calculate a persistable, unique pair of values that can be persisted in a database unique - * key and guarantee correct case-sensitivity. - *

- * While the short-string version of the value is always lowercase, the CRC is - * calculated from the virgin string if case-sensitivity is enforced; in the case-insensitive - * case, the CRC is calculated from a lowercase version of the string. - *

- * If the value is an empty string, then {@link #EMPTY_STRING} is used instead. This ensures - * that persisted values don't fall foul of the Oracle empty string comparison "behaviour" i.e - * you should never persist an empty string in Oracle as it equates to a SQL NULL. - * - * @param value the raw value that will be persisted - * @param dataLength the maximum number of characters that can be persisted - * @param useCharsFromStart true if the shortened string value must be made from - * the first characters of the string or false to use - * characters from the end of the string. - * @param caseSensitive true if the resulting pair must be case-sensitive or - * false if the pair must be case-insensitive. - * @return Return the persistable pair. The result will never be null, - * but the individual pair values will be null if the - * value given is null - */ - public static Pair getStringCrcPair( - String value, - int dataLength, - boolean useCharsFromStart, - boolean caseSensitive) - { - String valueLowerCase; - if (value == null) - { - return new Pair(null, null); - } - else if (value.length() == 0) - { - value = CrcHelper.EMPTY_STRING; - valueLowerCase = value; - } - else - { - valueLowerCase = value.toLowerCase(); - } - Long valueCrc; - try - { - CRC32 crc = new CRC32(); - if (caseSensitive) - { - crc.update(value.getBytes("UTF-8")); - } - else - { - crc.update(valueLowerCase.getBytes("UTF-8")); - } - valueCrc = crc.getValue(); - } - catch (UnsupportedEncodingException e) - { - throw new RuntimeException("UTF-8 encoding is not supported"); - } - // Crc Value will change based on the case-sensitive, So we need to get the short value based on case-sensitive - String valueShort = null; - String currentValue = caseSensitive ? value : valueLowerCase; - int valueLen = currentValue.length(); - if (valueLen < dataLength) - { - valueShort = currentValue; - } - else if (useCharsFromStart) - { - valueShort = currentValue.substring(0, dataLength - 1); - } - else - { - valueShort = currentValue.substring(valueLen - dataLength); - } - return new Pair(valueShort, valueCrc); - } -} \ No newline at end of file +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.repo.domain; + +import java.io.UnsupportedEncodingException; +import java.util.zip.CRC32; + +import org.alfresco.util.Pair; + +/** + * Helper class to calculate CRC values for string persistence. + * + * @author Derek Hulley + * @since 3.2 + */ +public class CrcHelper +{ + public static final String EMPTY_STRING = ".empty"; + + /** + * Calculate a persistable, unique pair of values that can be persisted in a database unique key and guarantee correct case-sensitivity. + *

+ * While the short-string version of the value is always lowercase, the CRC is calculated from the virgin string if case-sensitivity is enforced; in the case-insensitive case, the CRC is calculated from a lowercase version of the string. + *

+ * If the value is an empty string, then {@link #EMPTY_STRING} is used instead. This ensures that persisted values don't fall foul of the Oracle empty string comparison "behaviour" i.e you should never persist an empty string in Oracle as it equates to a SQL NULL. + * + * @param value + * the raw value that will be persisted + * @param dataLength + * the maximum number of characters that can be persisted + * @param useCharsFromStart + * true if the shortened string value must be made from the first characters of the string or false to use characters from the end of the string. + * @param caseSensitive + * true if the resulting pair must be case-sensitive or false if the pair must be case-insensitive. + * @return Return the persistable pair. The result will never be null, but the individual pair values will be null if the value given is null + */ + public static Pair getStringCrcPair( + String value, + int dataLength, + boolean useCharsFromStart, + boolean caseSensitive) + { + String valueLowerCase; + if (value == null) + { + return new Pair(null, null); + } + else if (value.length() == 0) + { + value = CrcHelper.EMPTY_STRING; + valueLowerCase = value; + } + else + { + valueLowerCase = value.toLowerCase(); + } + Long valueCrc; + try + { + CRC32 crc = new CRC32(); + if (caseSensitive) + { + crc.update(value.getBytes("UTF-8")); + } + else + { + crc.update(valueLowerCase.getBytes("UTF-8")); + } + valueCrc = crc.getValue(); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("UTF-8 encoding is not supported"); + } + // Get the short value (case-sensitive or not) + String valueShort = null; + int valueLen = valueLowerCase.length(); + if (valueLen < dataLength) + { + valueShort = valueLowerCase; + } + else if (useCharsFromStart) + { + valueShort = valueLowerCase.substring(0, dataLength - 1); + } + else + { + valueShort = valueLowerCase.substring(valueLen - dataLength); + } + return new Pair(valueShort, valueCrc); + + } +} diff --git a/repository/src/main/java/org/alfresco/repo/domain/audit/AuditDAO.java b/repository/src/main/java/org/alfresco/repo/domain/audit/AuditDAO.java index cac517706ef..0ffc67ee437 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/audit/AuditDAO.java +++ b/repository/src/main/java/org/alfresco/repo/domain/audit/AuditDAO.java @@ -1,258 +1,286 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.repo.domain.audit; - -import java.io.Serializable; -import java.net.URL; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.util.Pair; - -/** - * DAO services for alf_audit_XXX tables. - *

- * The older methods are supported by a different implementation and will eventually - * be deprecated and phased out. - * - * @author Derek Hulley - * @since 3.2 - */ -public interface AuditDAO -{ - /* - * V3.2 methods after here only, please - */ - - /** - * Information about the audit application to be passed in an out of the interface. - * - * @author Derek Hulley - * @since 3.2 - */ - public static class AuditApplicationInfo - { - private Long id; - private String name; - private Long modelId; - private Long disabledPathsId; - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append("AuditApplicationInfo ") - .append("[ id=").append(id) - .append(", name=").append(name) - .append(", modelId=").append(modelId) - .append(", disabledPathsId=").append(disabledPathsId) - .append("]"); - return sb.toString(); - } - - public Long getId() - { - return id; - } - public void setId(Long id) - { - this.id = id; - } - public String getName() - { - return name; - } - public void setname(String name) - { - this.name = name; - } - public Long getModelId() - { - return modelId; - } - public void setModelId(Long modelId) - { - this.modelId = modelId; - } - public Long getDisabledPathsId() - { - return disabledPathsId; - } - public void setDisabledPathsId(Long disabledPathsId) - { - this.disabledPathsId = disabledPathsId; - } - } - - /** - * Creates a new audit model entry or finds an existing one - * - * @param url the URL of the configuration - * @return Returns the ID of the config matching the input stream and the - * content storage details - * @since 3.2 - */ - Pair getOrCreateAuditModel(URL url); - - /** - * Get the audit application details. - * - * @param applicationName the name of the application - * @return Returns details of an existing application or null if it doesn't exist - * - * @since 3.2 - */ - AuditApplicationInfo getAuditApplication(String applicationName); - - /** - * Creates a new audit application. The application name must be unique. - * - * @param application the name of the application - * @param modelId the ID of the model configuration - * - * @since 3.2 - */ - AuditApplicationInfo createAuditApplication(String application, Long modelId); - - /** - * Update the audit application to refer to a new model. - * If the model did not change, then nothing will be done. - * - * @param id the ID of the audit application - * @param modelId the ID of the new model - * - * @since 3.2 - */ - void updateAuditApplicationModel(Long id, Long modelId); - - /** - * Update the audit application to hold a new set of disabled paths. - * If the value did not change, then nothing will be done. - * - * @param id the ID of the audit application - * @param disabledPaths the new disabled paths - * - * @since 3.2 - */ - void updateAuditApplicationDisabledPaths(Long id, Set disabledPaths); - - /** - * Delete audit entries for the application, possibly limiting the time range. - * - * @param applicationId an existing audit application ID - * @param fromTime the minimum entry time (inclusive, optional) - * @param toTime the maximum entry time (exclusive, optional) - * @return Returns the number of entries deleted - * - * @since 3.2 - */ - int deleteAuditEntries(Long applicationId, Long fromTime, Long toTime); - - /** - * Delete audit entries for the application for given id range. - * - * @param applicationId an existing audit application ID - * @param fromId the minimum fromId (inclusive, optional) - * @param toId the maximum toId (exclusive, optional) - * @return Returns the number of entries deleted - * - * @since 5.2.2 - */ - int deleteAuditEntriesByIdRange(Long applicationId, Long fromId, Long toId); - - /** - * Delete a discrete list of audit entries. Duplicate entries are collapsed - * and the number of entries deleted will match the count of unique IDs in - * the list; otherwise a concurrency condition has occured and an exception - * will be generated. - * - * @param auditEntryIds the IDs of all audit entries to delete - * @return Returns the number of entries deleted - */ - int deleteAuditEntries(List auditEntryIds); - - /** - * Create a new audit entry with the given map of values. - * - * @param applicationId an existing audit application ID - * @param time the time (ms since epoch) to log the entry against - * @param username the authenticated user (null if not present) - * @param values the values to record - * @return Returns the unique entry ID - * - * @since 3.2 - */ - Long createAuditEntry(Long applicationId, long time, String username, Map values); - - /** - * Find audit entries using the given parameters, any of which may be null - * - * @param callback the data callback per entry - * @param parameters the parameters for the query (may not be null) - * @param maxResults the maximum number of results to retrieve (must be greater than 0) - * - * @throws IllegalArgumentException if maxResults less or equal to zero - */ - void findAuditEntries( - AuditQueryCallback callback, - org.alfresco.service.cmr.audit.AuditQueryParameters parameters, - int maxResults); - - /** - * Issue an audit query to retrieve min / max audit record id for a given application. - * - * @param appId the database id of the application - * @param extremes a list containing min/max or both - * @return a map containing min/max and the associated value - */ - HashMap getAuditMinMaxByApp(long appId, List extremes); - - /** - * Issue an audit query to retrieve count of records for a given application. - * - * @param applicationId the database id of the application - * @return a map containing min/max and the associated value - */ - default int getAuditEntriesCountByApp(long applicationId) - { - return -1; - } - - /** - * Issue an audit query to retrieve count of records for a given application and properties - * - * @param parameters audit parameters provided by the where clause on the ReST API - * @return a map containing min/max and the associated value - */ - default int getAuditEntriesCountByAppAndProperties(org.alfresco.service.cmr.audit.AuditQueryParameters parameters) - { - return -1; - } -} \ No newline at end of file +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.domain.audit; + +import java.io.Serializable; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.util.Pair; + +/** + * DAO services for alf_audit_XXX tables. + *

+ * The older methods are supported by a different implementation and will eventually be deprecated and phased out. + * + * @author Derek Hulley + * @since 3.2 + */ +public interface AuditDAO +{ + /* V3.2 methods after here only, please */ + + /** + * Information about the audit application to be passed in an out of the interface. + * + * @author Derek Hulley + * @since 3.2 + */ + public static class AuditApplicationInfo + { + private Long id; + private String name; + private Long modelId; + private Long disabledPathsId; + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("AuditApplicationInfo ") + .append("[ id=").append(id) + .append(", name=").append(name) + .append(", modelId=").append(modelId) + .append(", disabledPathsId=").append(disabledPathsId) + .append("]"); + return sb.toString(); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getName() + { + return name; + } + + public void setname(String name) + { + this.name = name; + } + + public Long getModelId() + { + return modelId; + } + + public void setModelId(Long modelId) + { + this.modelId = modelId; + } + + public Long getDisabledPathsId() + { + return disabledPathsId; + } + + public void setDisabledPathsId(Long disabledPathsId) + { + this.disabledPathsId = disabledPathsId; + } + } + + /** + * Creates a new audit model entry or finds an existing one + * + * @param url + * the URL of the configuration + * @return Returns the ID of the config matching the input stream and the content storage details + * @since 3.2 + */ + Pair getOrCreateAuditModel(URL url); + + /** + * Get the audit application details. + * + * @param applicationName + * the name of the application + * @return Returns details of an existing application or null if it doesn't exist + * + * @since 3.2 + */ + AuditApplicationInfo getAuditApplication(String applicationName); + + /** + * Creates a new audit application. The application name must be unique. + * + * @param application + * the name of the application + * @param modelId + * the ID of the model configuration + * + * @since 3.2 + */ + AuditApplicationInfo createAuditApplication(String application, Long modelId); + + /** + * Update the audit application to refer to a new model. If the model did not change, then nothing will be done. + * + * @param id + * the ID of the audit application + * @param modelId + * the ID of the new model + * + * @since 3.2 + */ + void updateAuditApplicationModel(Long id, Long modelId); + + /** + * Update the audit application to hold a new set of disabled paths. If the value did not change, then nothing will be done. + * + * @param id + * the ID of the audit application + * @param disabledPaths + * the new disabled paths + * + * @since 3.2 + */ + void updateAuditApplicationDisabledPaths(Long id, Set disabledPaths); + + /** + * Delete audit entries for the application, possibly limiting the time range. + * + * @param applicationId + * an existing audit application ID + * @param fromTime + * the minimum entry time (inclusive, optional) + * @param toTime + * the maximum entry time (exclusive, optional) + * @return Returns the number of entries deleted + * + * @since 3.2 + */ + int deleteAuditEntries(Long applicationId, Long fromTime, Long toTime); + + /** + * Delete audit entries for the application for given id range. + * + * @param applicationId + * an existing audit application ID + * @param fromId + * the minimum fromId (inclusive, optional) + * @param toId + * the maximum toId (exclusive, optional) + * @return Returns the number of entries deleted + * + * @since 5.2.2 + */ + int deleteAuditEntriesByIdRange(Long applicationId, Long fromId, Long toId); + + /** + * Delete a discrete list of audit entries. Duplicate entries are collapsed and the number of entries deleted will match the count of unique IDs in the list; otherwise a concurrency condition has occured and an exception will be generated. + * + * @param auditEntryIds + * the IDs of all audit entries to delete + * @return Returns the number of entries deleted + */ + int deleteAuditEntries(List auditEntryIds); + + /** + * Create a new audit entry with the given map of values. + * + * @param applicationId + * an existing audit application ID + * @param time + * the time (ms since epoch) to log the entry against + * @param username + * the authenticated user (null if not present) + * @param values + * the values to record + * @return Returns the unique entry ID + * + * @since 3.2 + */ + Long createAuditEntry(Long applicationId, long time, String username, Map values); + + /** + * Find audit entries using the given parameters, any of which may be null + * + * @param callback + * the data callback per entry + * @param parameters + * the parameters for the query (may not be null) + * @param maxResults + * the maximum number of results to retrieve (must be greater than 0) + * + * @throws IllegalArgumentException + * if maxResults less or equal to zero + */ + void findAuditEntries( + AuditQueryCallback callback, + org.alfresco.service.cmr.audit.AuditQueryParameters parameters, + int maxResults); + + /** + * Issue an audit query to retrieve min / max audit record id for a given application. + * + * @param appId + * the database id of the application + * @param extremes + * a list containing min/max or both + * @return a map containing min/max and the associated value + */ + HashMap getAuditMinMaxByApp(long appId, List extremes); + + /** + * Issue an audit query to retrieve count of records for a given application. + * + * @param applicationId + * the database id of the application + * @return a map containing min/max and the associated value + */ + default int getAuditEntriesCountByApp(long applicationId) + { + return -1; + } + + /** + * Issue an audit query to retrieve count of records for a given application and properties + * + * @param applicationName + * name of the application to be queried + * @param parameters + * audit parameters provided by the where clause on the ReST API + * @return a map containing min/max and the associated value + */ + default int getAuditEntriesCountByAppAndProperties(String applicationName, org.alfresco.service.cmr.audit.AuditQueryParameters parameters) + { + return -1; + + } +} diff --git a/repository/src/main/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java b/repository/src/main/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java index 12468dbf8e0..abcce46aeb3 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java +++ b/repository/src/main/java/org/alfresco/repo/domain/audit/ibatis/AuditDAOImpl.java @@ -1,316 +1,315 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.repo.domain.audit.ibatis; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.alfresco.repo.domain.audit.AbstractAuditDAOImpl; -import org.alfresco.repo.domain.audit.AuditApplicationEntity; -import org.alfresco.repo.domain.audit.AuditDeleteParameters; -import org.alfresco.repo.domain.audit.AuditEntryEntity; -import org.alfresco.repo.domain.audit.AuditModelEntity; -import org.alfresco.repo.domain.audit.AuditQueryParameters; -import org.alfresco.repo.domain.audit.AuditQueryResult; -import org.alfresco.repo.domain.propval.PropertyValueDAO.PropertyFinderCallback; -import org.alfresco.util.Pair; -import org.apache.ibatis.session.RowBounds; -import org.mybatis.spring.SqlSessionTemplate; -import org.springframework.dao.ConcurrencyFailureException; - -/** - * iBatis-specific implementation of the DAO for alf_audit_XXX tables. - * - * @author Derek Hulley - * @since 3.2 - */ -public class AuditDAOImpl extends AbstractAuditDAOImpl -{ - private static final String SELECT_MODEL_BY_CRC = "alfresco.audit.select_AuditModelByCrc"; - private static final String INSERT_MODEL = "alfresco.audit.insert.insert_AuditModel"; - - private static final String SELECT_APPLICATION_BY_ID = "alfresco.audit.select_AuditApplicationById"; - private static final String SELECT_APPLICATION_BY_NAME_ID = "alfresco.audit.select_AuditApplicationByNameId"; - private static final String INSERT_APPLICATION = "alfresco.audit.insert.insert_AuditApplication"; - private static final String UPDATE_APPLICATION = "alfresco.audit.update_AuditApplication"; - - private static final String DELETE_ENTRIES = "alfresco.audit.delete_AuditEntries"; - private static final String DELETE_ENTRIES_BY_ID = "alfresco.audit.delete_AuditEntriesById"; - private static final String INSERT_ENTRY = "alfresco.audit.insert.insert_AuditEntry"; - private static final String SELECT_MINMAX_ENTRY_FOR_APP = "alfresco.audit.select_MinMaxAuditEntryId"; - private static final String SELECT_COUNT_ENTRIES_FOR_APP = "alfresco.audit.select_CountAuditEntryId"; - private static final String SELECT_COUNT_ENTRIES_FOR_APP_WITH_PROPERTIES = "select_CountAuditEntryIdWithWhereClause"; - - @SuppressWarnings("unused") - private static final String SELECT_ENTRIES_SIMPLE = "alfresco.audit.select_AuditEntriesSimple"; - private static final String SELECT_ENTRIES_WITH_VALUES = "alfresco.audit.select_AuditEntriesWithValues"; - private static final String SELECT_ENTRIES_WITHOUT_VALUES = "alfresco.audit.select_AuditEntriesWithoutValues"; - - private SqlSessionTemplate template; - - public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) - { - this.template = sqlSessionTemplate; - } - - @Override - protected AuditModelEntity getAuditModelByCrc(long crc) - { - AuditModelEntity entity = new AuditModelEntity(); - entity.setContentCrc(crc); - entity = template.selectOne( - SELECT_MODEL_BY_CRC, - entity); - // Done - return entity; - } - - @Override - protected AuditModelEntity createAuditModel(Long contentDataId, long crc) - { - AuditModelEntity entity = new AuditModelEntity(); - entity.setContentDataId(contentDataId); - entity.setContentCrc(crc); - template.insert(INSERT_MODEL, entity); - return entity; - } - - @Override - protected AuditApplicationEntity getAuditApplicationById(Long id) - { - Map params = new HashMap(11); - params.put("id", id); - AuditApplicationEntity entity = template.selectOne( - SELECT_APPLICATION_BY_ID, - params); - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Searched for audit application ID " + id + " and found: " + entity); - } - return entity; - } - - @Override - protected AuditApplicationEntity getAuditApplicationByName(String appName) - { - // Resolve the name as a property ID - Pair appNamePair = propertyValueDAO.getPropertyValue(appName); - if (appNamePair == null) - { - // There will be no results - return null; - } - - Map params = new HashMap(11); - params.put("id", appNamePair.getFirst()); - AuditApplicationEntity entity = template.selectOne( - SELECT_APPLICATION_BY_NAME_ID, - params); - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Searched for audit application '" + appName + "' and found: " + entity); - } - return entity; - } - - @Override - protected AuditApplicationEntity createAuditApplication(Long appNameId, Long modelId, Long disabledPathsId) - { - AuditApplicationEntity entity = new AuditApplicationEntity(); - entity.setVersion((short)0); - entity.setApplicationNameId(appNameId); - entity.setAuditModelId(modelId); - entity.setDisabledPathsId(disabledPathsId); - template.insert(INSERT_APPLICATION, entity); - return entity; - } - - @Override - protected AuditApplicationEntity updateAuditApplication(AuditApplicationEntity entity) - { - AuditApplicationEntity updateEntity = new AuditApplicationEntity(); - updateEntity.setId(entity.getId()); - updateEntity.setVersion(entity.getVersion()); - updateEntity.incrementVersion(); - updateEntity.setApplicationNameId(entity.getApplicationNameId()); - updateEntity.setAuditModelId(entity.getAuditModelId()); - updateEntity.setDisabledPathsId(entity.getDisabledPathsId()); - - int updated = template.update(UPDATE_APPLICATION, updateEntity); - if (updated != 1) - { - // unexpected number of rows affected - throw new ConcurrencyFailureException("Incorrect number of rows affected for updateAuditApplication: " + updateEntity + ": expected 1, actual " + updated); - } - - // Done - return updateEntity; - } - - public int deleteAuditEntries(Long applicationId, Long from, Long to) - { - AuditDeleteParameters params = new AuditDeleteParameters(); - params.setAuditApplicationId(applicationId); - params.setAuditFromTime(from); - params.setAuditToTime(to); - return template.delete(DELETE_ENTRIES, params); - } - - public int deleteAuditEntriesByIdRange(Long applicationId, Long fromId, Long toId) - { - AuditDeleteParameters params = new AuditDeleteParameters(); - params.setAuditApplicationId(applicationId); - params.setAuditFromId(fromId); - params.setAuditToId(toId); - return template.delete(DELETE_ENTRIES, params); - } - - @Override - protected int deleteAuditEntriesImpl(List auditEntryIds) - { - AuditDeleteParameters params = new AuditDeleteParameters(); - params.setAuditEntryIds(auditEntryIds); - return template.delete(DELETE_ENTRIES_BY_ID, params); - } - - @Override - protected AuditEntryEntity createAuditEntry(Long applicationId, long time, Long usernameId, Long valuesId) - { - AuditEntryEntity entity = new AuditEntryEntity(); - entity.setAuditApplicationId(applicationId); - entity.setAuditTime(time); - entity.setAuditUserId(usernameId); - entity.setAuditValuesId(valuesId); - template.insert(INSERT_ENTRY, entity); - return entity; - } - - public HashMap getAuditMinMaxByApp(long appId, List extremes) - { - // Build parameters to be used in the query. Filter the duplicates when inserting into map - Map params = extremes.stream().collect(Collectors.toMap(s -> s, s -> Boolean.TRUE, (s1, s2) -> s1)); - params.put("auditAppId", appId); - - HashMap result = template.selectOne(SELECT_MINMAX_ENTRY_FOR_APP, params); - - return result; - } - - @Override - public int getAuditEntriesCountByApp(long applicationId) - { - Map params = new HashMap<>(); - params.put("auditAppId", applicationId); - - int result = template.selectOne(SELECT_COUNT_ENTRIES_FOR_APP, params); - - return result; - } - - @Override - public int getAuditEntriesCountByAppAndProperties(org.alfresco.service.cmr.audit.AuditQueryParameters parameters) - { - AuditQueryParameters dbParameters = convertFromRestAuditQueryParameters(parameters); - - int result = template.selectOne(SELECT_COUNT_ENTRIES_FOR_APP_WITH_PROPERTIES, dbParameters); - - return result; - } - - @SuppressWarnings("unchecked") - @Override - protected void findAuditEntries( - final AuditQueryRowHandler rowHandler, - int maxResults, - org.alfresco.service.cmr.audit.AuditQueryParameters restParameters) - { - AuditQueryParameters params = convertFromRestAuditQueryParameters(restParameters); - if (params==null) - { - return; - } - - if (maxResults > 0) - { - // Query without getting the values. We gather all the results and batch-fetch the audited - // values afterwards. - final Map resultsByValueId = new HashMap(173); - PropertyFinderCallback propertyFinderCallback = new PropertyFinderCallback() - { - public void handleProperty(Long id, Serializable value) - { - // get the row - AuditQueryResult row = resultsByValueId.get(id); - try - { - row.setAuditValue((Map) value); - } - catch (ClassCastException e) - { - // The handler will deal with the entry - } - } - }; - - List rows = template.selectList(SELECT_ENTRIES_WITHOUT_VALUES, params, new RowBounds(0, maxResults)); - for (AuditQueryResult row : rows) - { - resultsByValueId.put(row.getAuditValuesId(), row); - if (resultsByValueId.size() >= 100) - { - // Fetch values for the results. The treemap is ordered. - List valueIds = new ArrayList(resultsByValueId.keySet()); - propertyValueDAO.getPropertiesByIds(valueIds, propertyFinderCallback); - // Clear and continue - resultsByValueId.clear(); - } - } - // Process any remaining results - if (resultsByValueId.size() > 0) - { - // Fetch values for the results. The treemap is ordered. - 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 - { - throw new IllegalArgumentException("maxResults must be greater than 0"); - } - } -} - +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.domain.audit.ibatis; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.ibatis.session.RowBounds; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.dao.ConcurrencyFailureException; + +import org.alfresco.repo.domain.audit.AbstractAuditDAOImpl; +import org.alfresco.repo.domain.audit.AuditApplicationEntity; +import org.alfresco.repo.domain.audit.AuditDeleteParameters; +import org.alfresco.repo.domain.audit.AuditEntryEntity; +import org.alfresco.repo.domain.audit.AuditModelEntity; +import org.alfresco.repo.domain.audit.AuditQueryParameters; +import org.alfresco.repo.domain.audit.AuditQueryResult; +import org.alfresco.repo.domain.propval.PropertyValueDAO.PropertyFinderCallback; +import org.alfresco.util.Pair; + +/** + * iBatis-specific implementation of the DAO for alf_audit_XXX tables. + * + * @author Derek Hulley + * @since 3.2 + */ +public class AuditDAOImpl extends AbstractAuditDAOImpl +{ + private static final String SELECT_MODEL_BY_CRC = "alfresco.audit.select_AuditModelByCrc"; + private static final String INSERT_MODEL = "alfresco.audit.insert.insert_AuditModel"; + + private static final String SELECT_APPLICATION_BY_ID = "alfresco.audit.select_AuditApplicationById"; + private static final String SELECT_APPLICATION_BY_NAME_ID = "alfresco.audit.select_AuditApplicationByNameId"; + private static final String INSERT_APPLICATION = "alfresco.audit.insert.insert_AuditApplication"; + private static final String UPDATE_APPLICATION = "alfresco.audit.update_AuditApplication"; + + private static final String DELETE_ENTRIES = "alfresco.audit.delete_AuditEntries"; + private static final String DELETE_ENTRIES_BY_ID = "alfresco.audit.delete_AuditEntriesById"; + private static final String INSERT_ENTRY = "alfresco.audit.insert.insert_AuditEntry"; + private static final String SELECT_MINMAX_ENTRY_FOR_APP = "alfresco.audit.select_MinMaxAuditEntryId"; + private static final String SELECT_COUNT_ENTRIES_FOR_APP = "alfresco.audit.select_CountAuditEntryId"; + private static final String SELECT_COUNT_ENTRIES_FOR_APP_WITH_PROPERTIES = "select_CountAuditEntryIdWithWhereClause"; + + @SuppressWarnings("unused") + private static final String SELECT_ENTRIES_SIMPLE = "alfresco.audit.select_AuditEntriesSimple"; + private static final String SELECT_ENTRIES_WITH_VALUES = "alfresco.audit.select_AuditEntriesWithValues"; + private static final String SELECT_ENTRIES_WITHOUT_VALUES = "alfresco.audit.select_AuditEntriesWithoutValues"; + + private SqlSessionTemplate template; + + public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) + { + this.template = sqlSessionTemplate; + } + + @Override + protected AuditModelEntity getAuditModelByCrc(long crc) + { + AuditModelEntity entity = new AuditModelEntity(); + entity.setContentCrc(crc); + entity = template.selectOne( + SELECT_MODEL_BY_CRC, + entity); + // Done + return entity; + } + + @Override + protected AuditModelEntity createAuditModel(Long contentDataId, long crc) + { + AuditModelEntity entity = new AuditModelEntity(); + entity.setContentDataId(contentDataId); + entity.setContentCrc(crc); + template.insert(INSERT_MODEL, entity); + return entity; + } + + @Override + protected AuditApplicationEntity getAuditApplicationById(Long id) + { + Map params = new HashMap(11); + params.put("id", id); + AuditApplicationEntity entity = template.selectOne( + SELECT_APPLICATION_BY_ID, + params); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Searched for audit application ID " + id + " and found: " + entity); + } + return entity; + } + + @Override + protected AuditApplicationEntity getAuditApplicationByName(String appName) + { + // Resolve the name as a property ID + Pair appNamePair = propertyValueDAO.getPropertyValue(appName); + if (appNamePair == null) + { + // There will be no results + return null; + } + + Map params = new HashMap(11); + params.put("id", appNamePair.getFirst()); + AuditApplicationEntity entity = template.selectOne( + SELECT_APPLICATION_BY_NAME_ID, + params); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Searched for audit application '" + appName + "' and found: " + entity); + } + return entity; + } + + @Override + protected AuditApplicationEntity createAuditApplication(Long appNameId, Long modelId, Long disabledPathsId) + { + AuditApplicationEntity entity = new AuditApplicationEntity(); + entity.setVersion((short) 0); + entity.setApplicationNameId(appNameId); + entity.setAuditModelId(modelId); + entity.setDisabledPathsId(disabledPathsId); + template.insert(INSERT_APPLICATION, entity); + return entity; + } + + @Override + protected AuditApplicationEntity updateAuditApplication(AuditApplicationEntity entity) + { + AuditApplicationEntity updateEntity = new AuditApplicationEntity(); + updateEntity.setId(entity.getId()); + updateEntity.setVersion(entity.getVersion()); + updateEntity.incrementVersion(); + updateEntity.setApplicationNameId(entity.getApplicationNameId()); + updateEntity.setAuditModelId(entity.getAuditModelId()); + updateEntity.setDisabledPathsId(entity.getDisabledPathsId()); + + int updated = template.update(UPDATE_APPLICATION, updateEntity); + if (updated != 1) + { + // unexpected number of rows affected + throw new ConcurrencyFailureException("Incorrect number of rows affected for updateAuditApplication: " + updateEntity + ": expected 1, actual " + updated); + } + + // Done + return updateEntity; + } + + public int deleteAuditEntries(Long applicationId, Long from, Long to) + { + AuditDeleteParameters params = new AuditDeleteParameters(); + params.setAuditApplicationId(applicationId); + params.setAuditFromTime(from); + params.setAuditToTime(to); + return template.delete(DELETE_ENTRIES, params); + } + + public int deleteAuditEntriesByIdRange(Long applicationId, Long fromId, Long toId) + { + AuditDeleteParameters params = new AuditDeleteParameters(); + params.setAuditApplicationId(applicationId); + params.setAuditFromId(fromId); + params.setAuditToId(toId); + return template.delete(DELETE_ENTRIES, params); + } + + @Override + protected int deleteAuditEntriesImpl(List auditEntryIds) + { + AuditDeleteParameters params = new AuditDeleteParameters(); + params.setAuditEntryIds(auditEntryIds); + return template.delete(DELETE_ENTRIES_BY_ID, params); + } + + @Override + protected AuditEntryEntity createAuditEntry(Long applicationId, long time, Long usernameId, Long valuesId) + { + AuditEntryEntity entity = new AuditEntryEntity(); + entity.setAuditApplicationId(applicationId); + entity.setAuditTime(time); + entity.setAuditUserId(usernameId); + entity.setAuditValuesId(valuesId); + template.insert(INSERT_ENTRY, entity); + return entity; + } + + public HashMap getAuditMinMaxByApp(long appId, List extremes) + { + // Build parameters to be used in the query. Filter the duplicates when inserting into map + Map params = extremes.stream().collect(Collectors.toMap(s -> s, s -> Boolean.TRUE, (s1, s2) -> s1)); + params.put("auditAppId", appId); + + HashMap result = template.selectOne(SELECT_MINMAX_ENTRY_FOR_APP, params); + + return result; + } + + @Override + public int getAuditEntriesCountByApp(long applicationId) + { + Map params = new HashMap<>(); + params.put("auditAppId", applicationId); + + int result = template.selectOne(SELECT_COUNT_ENTRIES_FOR_APP, params); + + return result; + } + + @Override + public int getAuditEntriesCountByAppAndProperties(String applicationName, org.alfresco.service.cmr.audit.AuditQueryParameters parameters) + { + AuditQueryParameters dbParameters = convertFromRestAuditQueryParameters(parameters); + + int result = template.selectOne(SELECT_COUNT_ENTRIES_FOR_APP_WITH_PROPERTIES, dbParameters); + + return result; + } + + @SuppressWarnings("unchecked") + @Override + protected void findAuditEntries( + final AuditQueryRowHandler rowHandler, + int maxResults, + org.alfresco.service.cmr.audit.AuditQueryParameters restParameters) + { + AuditQueryParameters params = convertFromRestAuditQueryParameters(restParameters); + if (params == null) + { + return; + } + + if (maxResults > 0) + { + // Query without getting the values. We gather all the results and batch-fetch the audited + // values afterwards. + final Map resultsByValueId = new HashMap(173); + PropertyFinderCallback propertyFinderCallback = new PropertyFinderCallback() { + public void handleProperty(Long id, Serializable value) + { + // get the row + AuditQueryResult row = resultsByValueId.get(id); + try + { + row.setAuditValue((Map) value); + } + catch (ClassCastException e) + { + // The handler will deal with the entry + } + } + }; + + List rows = template.selectList(SELECT_ENTRIES_WITHOUT_VALUES, params, new RowBounds(0, maxResults)); + for (AuditQueryResult row : rows) + { + resultsByValueId.put(row.getAuditValuesId(), row); + if (resultsByValueId.size() >= 100) + { + // Fetch values for the results. The treemap is ordered. + List valueIds = new ArrayList(resultsByValueId.keySet()); + propertyValueDAO.getPropertiesByIds(valueIds, propertyFinderCallback); + // Clear and continue + resultsByValueId.clear(); + } + } + // Process any remaining results + if (resultsByValueId.size() > 0) + { + // Fetch values for the results. The treemap is ordered. + 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 + { + throw new IllegalArgumentException("maxResults must be greater than 0"); + } + } +} diff --git a/repository/src/main/java/org/alfresco/repo/domain/propval/PropertyStringValueEntity.java b/repository/src/main/java/org/alfresco/repo/domain/propval/PropertyStringValueEntity.java index 56e188fbf67..8b07cd465d9 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/propval/PropertyStringValueEntity.java +++ b/repository/src/main/java/org/alfresco/repo/domain/propval/PropertyStringValueEntity.java @@ -1,173 +1,159 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.repo.domain.propval; - -import org.alfresco.repo.domain.CrcHelper; -import org.alfresco.util.EqualsHelper; -import org.alfresco.util.Pair; - -/** - * Entity bean for alf_prop_string_value table. - * - * @author Derek Hulley - * @since 3.2 - */ -public class PropertyStringValueEntity -{ - public static final String EMPTY_STRING = ""; - public static final String EMPTY_STRING_REPLACEMENT = ".empty"; - - private Long id; - private String stringValue; - private String stringEndLower; - private Long stringCrc; - private String stringLower; - - public PropertyStringValueEntity() - { - } - - @Override - public int hashCode() - { - return (stringValue == null ? 0 : stringValue.hashCode()); - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - else if (obj != null && obj instanceof PropertyStringValueEntity) - { - PropertyStringValueEntity that = (PropertyStringValueEntity) obj; - return EqualsHelper.nullSafeEquals(this.stringValue, that.stringValue); - } - else - { - return false; - } - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(512); - sb.append("PropertyStringValueEntity") - .append("[ ID=").append(id) - .append(", stringValue=").append(stringValue) - .append("]"); - return sb.toString(); - } - - public Pair getEntityPair() - { - if (stringValue != null && stringValue.equals(PropertyStringValueEntity.EMPTY_STRING_REPLACEMENT)) - { - return new Pair(id, PropertyStringValueEntity.EMPTY_STRING); - } - else - { - return new Pair(id, stringValue); - } - } - - /** - * Set the string and string-end values - */ - public void setValue(String value) - { - if (value == null) - { - throw new IllegalArgumentException("Null strings cannot be persisted"); - } - if (value != null && value.equals(PropertyStringValueEntity.EMPTY_STRING)) - { - // Oracle: We can't insert empty strings into the column. - value = PropertyStringValueEntity.EMPTY_STRING_REPLACEMENT; - } - stringValue = value; - // Calculate the crc value from the original value - Pair crcPair = CrcHelper.getStringCrcPair(value, 16, false, true); - stringEndLower = crcPair.getFirst(); - stringCrc = crcPair.getSecond(); - // Calculate the crc value with case-insensitive - Pair crcPairWithCaseInSensitive = CrcHelper.getStringCrcPair(value, 16, false, false); - stringLower = crcPairWithCaseInSensitive.getFirst(); - } - - public Long getId() - { - return id; - } - - public void setId(Long id) - { - this.id = id; - } - - public String getStringValue() - { - return stringValue; - } - - public void setStringValue(String stringValue) - { - this.stringValue = stringValue; - } - - public String getStringEndLower() - { - return stringEndLower; - } - - public void setStringEndLower(String stringEndLower) - { - this.stringEndLower = stringEndLower; - } - - public Long getStringCrc() - { - return stringCrc; - } - - public void setStringCrc(Long stringCrc) - { - this.stringCrc = stringCrc; - } - - public String getStringLower() - { - return stringLower; - } - - public void setStringLower(String stringLower) - { - this.stringLower = stringLower; - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.domain.propval; + +import org.alfresco.repo.domain.CrcHelper; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.Pair; + +/** + * Entity bean for alf_prop_string_value table. + * + * @author Derek Hulley + * @since 3.2 + */ +public class PropertyStringValueEntity +{ + public static final String EMPTY_STRING = ""; + public static final String EMPTY_STRING_REPLACEMENT = ".empty"; + + private Long id; + private String stringValue; + private String stringEndLower; + private Long stringCrc; + + public PropertyStringValueEntity() + {} + + @Override + public int hashCode() + { + return (stringValue == null ? 0 : stringValue.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && obj instanceof PropertyStringValueEntity) + { + PropertyStringValueEntity that = (PropertyStringValueEntity) obj; + return EqualsHelper.nullSafeEquals(this.stringValue, that.stringValue); + } + else + { + return false; + } + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("PropertyStringValueEntity") + .append("[ ID=").append(id) + .append(", stringValue=").append(stringValue) + .append("]"); + return sb.toString(); + } + + public Pair getEntityPair() + { + if (stringValue != null && stringValue.equals(PropertyStringValueEntity.EMPTY_STRING_REPLACEMENT)) + { + return new Pair(id, PropertyStringValueEntity.EMPTY_STRING); + } + else + { + return new Pair(id, stringValue); + } + } + + /** + * Set the string and string-end values + */ + public void setValue(String value) + { + if (value == null) + { + throw new IllegalArgumentException("Null strings cannot be persisted"); + } + if (value != null && value.equals(PropertyStringValueEntity.EMPTY_STRING)) + { + // Oracle: We can't insert empty strings into the column. + value = PropertyStringValueEntity.EMPTY_STRING_REPLACEMENT; + } + stringValue = value; + // Calculate the crc value from the original value + Pair crcPair = CrcHelper.getStringCrcPair(value, 16, false, true); + stringEndLower = crcPair.getFirst(); + stringCrc = crcPair.getSecond(); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getStringValue() + { + return stringValue; + } + + public void setStringValue(String stringValue) + { + this.stringValue = stringValue; + } + + public String getStringEndLower() + { + return stringEndLower; + } + + public void setStringEndLower(String stringEndLower) + { + this.stringEndLower = stringEndLower; + } + + public Long getStringCrc() + { + return stringCrc; + } + + public void setStringCrc(Long stringCrc) + { + this.stringCrc = stringCrc; + } + +} diff --git a/repository/src/main/java/org/alfresco/service/cmr/audit/AuditService.java b/repository/src/main/java/org/alfresco/service/cmr/audit/AuditService.java index bbb2f886ac5..79439ae2841 100644 --- a/repository/src/main/java/org/alfresco/service/cmr/audit/AuditService.java +++ b/repository/src/main/java/org/alfresco/service/cmr/audit/AuditService.java @@ -1,266 +1,299 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2024 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.service.cmr.audit; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * The public API by which applications can query the audit logs and enable or disable auditing. - * - * @author Derek Hulley - */ -public interface AuditService -{ - /** - * @return Returns true if auditing is globally enabled - * - * @since 3.4 - */ - boolean isAuditEnabled(); - - /** - * Enable or disable the global auditing state - * - * @param enable true to enable auditing globally or false to disable - * - * @since 3.4 - */ - void setAuditEnabled(boolean enable); - - /** - * Helper bean to carry information about an audit application. - * - * @author Derek Hulley - * @since 3.4 - */ - public static class AuditApplication - { - private final String name; - private final String key; - private final boolean enabled; - /** - * Constructor for final variables - */ - public AuditApplication(String name, String key, boolean enabled) - { - this.name = name; - this.key = key; - this.enabled = enabled; - } - public String getName() - { - return name; - } - public String getKey() - { - return key; - } - public boolean isEnabled() - { - return enabled; - } - } - - /** - * Get all registered audit applications - * - * @return Returns a map of audit applications keyed by their name - * - * @since 3.4 - */ - Map getAuditApplications(); - - /** - * @param applicationName the name of the application to check - * @param path the path to check - * @return Returns true if auditing is enabled for the given path - * - * @since 3.2 - */ - boolean isAuditEnabled(String applicationName, String path); - - /** - * Enable auditing for an application path - * - * @param applicationName the name of the application to check - * @param path the path to enable - * - * @since 3.2 - */ - void enableAudit(String applicationName, String path); - - /** - * Disable auditing for an application path - * - * @param applicationName the name of the application to check - * @param path the path to disable - * - * @since 3.2 - */ - void disableAudit(String applicationName, String path); - - /** - * Remove all audit entries for the given application - * - * @param applicationName the name of the application for which to remove entries - * @return Returns the number of audit entries deleted - * - * @since 3.2 - * - * @deprecated Use {@link #clearAudit(String, Long, Long)} - */ - int clearAudit(String applicationName); - - /** - * Remove audit entries for the given application between the time ranges. If no start - * time is given then entries are deleted as far back as they exist. If no end time is - * given then entries are deleted up until the current time. - * - * @param applicationName the name of the application for which to remove entries - * @param fromTime the start time of entries to remove (inclusive and optional) - * @param toTime the end time of entries to remove (exclusive and optional) - * @return Returns the number of audit entries deleted - * - * @since 3.4 - */ - int clearAudit(String applicationName, Long fromTime, Long toTime); - - /** - * Remove audit entries for the given application between the audit entry ids. - * - * @param applicationName the name of the application for which to remove entries - * @param fromId the start time of entries to remove (inclusive and optional) - * @param toId the end time of entries to remove (exclusive and optional) - * @return Returns the number of audit entries deleted - * - * @since 5.2.2 - */ - int clearAuditByIdRange(String applicationName, Long fromId, Long toId); - - /** - * Delete a discrete list of audit entries. - *

- * This method should not be called while processing - * {@link #auditQuery(AuditQueryCallback, AuditQueryParameters, int) query results}. - * - * @param auditEntryIds the IDs of all audit entries to delete - * @return Returns the number of audit entries deleted - * - * @since 3.4 - */ - int clearAudit(List auditEntryIds); - - /** - * The interface that will be used to give query results to the calling code. - * - * @since 3.2 - */ - public static interface AuditQueryCallback - { - /** - * Determines whether this callback requires the values argument to be populated when {@link #handleAuditEntry} - * is called. - * - * @return true if this callback requires the values argument to be populated - */ - boolean valuesRequired(); - - /** - * Handle a row of audit entry data. - * - * @param entryId the unique audit entry ID - * @param applicationName the name of the application - * @param user the user that logged the entry - * @param time the time of the entry - * @param values the values map as created - * @return Return true to continue processing rows or false to stop - */ - boolean handleAuditEntry( - Long entryId, - String applicationName, - 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); - } - - /** - * Issue an audit query using the given parameters and consuming results in the callback. - * Results are returned in entry order, corresponding to time order. - * - * @param callback the callback that will handle results - * @param parameters the parameters for the query (may not be null) - * @param maxResults the maximum number of results to retrieve (must be greater than 0) - * - * @throws IllegalArgumentException if maxResults less or equal to zero - * - * @since 3.3 - */ - void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults); - - /** - * Issue an audit query to retrieve min / max audit record id for a given application. - * - * @param applicationName the name of the application - * @param extremes a list containing min/max or both - * @return a map containing min/max and the associated value - */ - HashMap getAuditMinMaxByApp(String applicationName, List extremes); - - /** - * Issue an audit query to retrieve min / max audit record id for a given application. - * - * @param applicationName the name of the application - * @return a map containing min/max and the associated value - */ - default int getAuditEntriesCountByApp(String applicationName) - { - return -1; - } - - /** - * Issue an audit query to retrieve min / max audit record id for a given application and properties - * - * @param parameters audit parameters provided by the where clause on the ReST API - * @return a map containing min/max and the associated value - */ - default int getAuditEntriesCountByAppAndProperties(AuditQueryParameters parameters) - { - return -1; - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.service.cmr.audit; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The public API by which applications can query the audit logs and enable or disable auditing. + * + * @author Derek Hulley + */ +public interface AuditService +{ + /** + * @return Returns true if auditing is globally enabled + * + * @since 3.4 + */ + boolean isAuditEnabled(); + + /** + * Enable or disable the global auditing state + * + * @param enable + * true to enable auditing globally or false to disable + * + * @since 3.4 + */ + void setAuditEnabled(boolean enable); + + /** + * Helper bean to carry information about an audit application. + * + * @author Derek Hulley + * @since 3.4 + */ + public static class AuditApplication + { + private final String name; + private final String key; + private final boolean enabled; + + /** + * Constructor for final variables + */ + public AuditApplication(String name, String key, boolean enabled) + { + this.name = name; + this.key = key; + this.enabled = enabled; + } + + public String getName() + { + return name; + } + + public String getKey() + { + return key; + } + + public boolean isEnabled() + { + return enabled; + } + } + + /** + * Get all registered audit applications + * + * @return Returns a map of audit applications keyed by their name + * + * @since 3.4 + */ + Map getAuditApplications(); + + /** + * @param applicationName + * the name of the application to check + * @param path + * the path to check + * @return Returns true if auditing is enabled for the given path + * + * @since 3.2 + */ + boolean isAuditEnabled(String applicationName, String path); + + /** + * Enable auditing for an application path + * + * @param applicationName + * the name of the application to check + * @param path + * the path to enable + * + * @since 3.2 + */ + void enableAudit(String applicationName, String path); + + /** + * Disable auditing for an application path + * + * @param applicationName + * the name of the application to check + * @param path + * the path to disable + * + * @since 3.2 + */ + void disableAudit(String applicationName, String path); + + /** + * Remove all audit entries for the given application + * + * @param applicationName + * the name of the application for which to remove entries + * @return Returns the number of audit entries deleted + * + * @since 3.2 + * + * @deprecated Use {@link #clearAudit(String, Long, Long)} + */ + int clearAudit(String applicationName); + + /** + * Remove audit entries for the given application between the time ranges. If no start time is given then entries are deleted as far back as they exist. If no end time is given then entries are deleted up until the current time. + * + * @param applicationName + * the name of the application for which to remove entries + * @param fromTime + * the start time of entries to remove (inclusive and optional) + * @param toTime + * the end time of entries to remove (exclusive and optional) + * @return Returns the number of audit entries deleted + * + * @since 3.4 + */ + int clearAudit(String applicationName, Long fromTime, Long toTime); + + /** + * Remove audit entries for the given application between the audit entry ids. + * + * @param applicationName + * the name of the application for which to remove entries + * @param fromId + * the start time of entries to remove (inclusive and optional) + * @param toId + * the end time of entries to remove (exclusive and optional) + * @return Returns the number of audit entries deleted + * + * @since 5.2.2 + */ + int clearAuditByIdRange(String applicationName, Long fromId, Long toId); + + /** + * Delete a discrete list of audit entries. + *

+ * This method should not be called while processing {@link #auditQuery(AuditQueryCallback, AuditQueryParameters, int) query results}. + * + * @param auditEntryIds + * the IDs of all audit entries to delete + * @return Returns the number of audit entries deleted + * + * @since 3.4 + */ + int clearAudit(List auditEntryIds); + + /** + * The interface that will be used to give query results to the calling code. + * + * @since 3.2 + */ + public static interface AuditQueryCallback + { + /** + * Determines whether this callback requires the values argument to be populated when {@link #handleAuditEntry} is called. + * + * @return true if this callback requires the values argument to be populated + */ + boolean valuesRequired(); + + /** + * Handle a row of audit entry data. + * + * @param entryId + * the unique audit entry ID + * @param applicationName + * the name of the application + * @param user + * the user that logged the entry + * @param time + * the time of the entry + * @param values + * the values map as created + * @return Return true to continue processing rows or false to stop + */ + boolean handleAuditEntry( + Long entryId, + String applicationName, + 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); + } + + /** + * Issue an audit query using the given parameters and consuming results in the callback. Results are returned in entry order, corresponding to time order. + * + * @param callback + * the callback that will handle results + * @param parameters + * the parameters for the query (may not be null) + * @param maxResults + * the maximum number of results to retrieve (must be greater than 0) + * + * @throws IllegalArgumentException + * if maxResults less or equal to zero + * + * @since 3.3 + */ + void auditQuery(AuditQueryCallback callback, AuditQueryParameters parameters, int maxResults); + + /** + * Issue an audit query to retrieve min / max audit record id for a given application. + * + * @param applicationName + * the name of the application + * @param extremes + * a list containing min/max or both + * @return a map containing min/max and the associated value + */ + HashMap getAuditMinMaxByApp(String applicationName, List extremes); + + /** + * Issue an audit query to retrieve min / max audit record id for a given application. + * + * @param applicationName + * the name of the application + * @return a map containing min/max and the associated value + */ + default int getAuditEntriesCountByApp(String applicationName) + { + return -1; + } + + /** + * Issue an audit query to retrieve min / max audit record id for a given application and properties + * + * @param applicationName + * the name of the application + * @param parameters + * audit parameters provided by the where clause on the ReST API + * @return a map containing min/max and the associated value + */ + default int getAuditEntriesCountByAppAndProperties(String applicationName, AuditQueryParameters parameters) + { + return -1; + + } +}