diff --git a/config/alfresco/audit/alfresco-audit-3.2.xsd b/config/alfresco/audit/alfresco-audit-3.2.xsd index 810d82f275..f050c984e0 100644 --- a/config/alfresco/audit/alfresco-audit-3.2.xsd +++ b/config/alfresco/audit/alfresco-audit-3.2.xsd @@ -19,6 +19,7 @@ + @@ -50,11 +51,25 @@ + + + + + + + + + + + + + + @@ -107,6 +122,14 @@ + + + + + + + + diff --git a/config/alfresco/audit/alfresco-audit-repository.xml b/config/alfresco/audit/alfresco-audit-repository.xml index a3c2e115cb..0037f565bf 100644 --- a/config/alfresco/audit/alfresco-audit-repository.xml +++ b/config/alfresco/audit/alfresco-audit-repository.xml @@ -20,6 +20,10 @@ + + + + diff --git a/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java b/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java index 4594275347..3e49fa9909 100644 --- a/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java +++ b/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java @@ -35,6 +35,7 @@ import org.alfresco.repo.audit.model.AuditApplication; import org.alfresco.repo.audit.model.AuditModelException; import org.alfresco.repo.audit.model.AuditModelRegistry; import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PathMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; @@ -51,6 +52,7 @@ import org.springframework.util.ResourceUtils; public class AuditBootstrapTest extends TestCase { private static final String APPLICATION_TEST = "Alfresco Test"; + private static final String KEY_TEST = "test"; private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private static final Log logger = LogFactory.getLog(AuditBootstrapTest.class); @@ -121,12 +123,33 @@ public class AuditBootstrapTest extends TestCase public void testGetApplicationId() { - AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST); + AuditApplication app = auditModelRegistry.getAuditApplicationByName(APPLICATION_TEST); assertNotNull(app); Long appId = app.getApplicationId(); assertNotNull("No audit application ID for " + APPLICATION_TEST, appId); } + public void testGetApplicationByKey() + { + AuditApplication app = auditModelRegistry.getAuditApplicationByKey(KEY_TEST); + assertNotNull(app); + } + + public void testGetPathMappings() + { + PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); + assertNotNull(pathMapper); + try + { + pathMapper.addPathMap("x", "y"); + fail("Should not be allowed to update the path mappings."); + } + catch (Throwable e) + { + // Expected + } + } + private void testBadPath(AuditApplication app, String path) { try @@ -142,7 +165,7 @@ public class AuditBootstrapTest extends TestCase public void testAuditApplication_Path() { - AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST); + AuditApplication app = auditModelRegistry.getAuditApplicationByName(APPLICATION_TEST); assertNotNull(app); // Check that path checks are working @@ -155,7 +178,7 @@ public class AuditBootstrapTest extends TestCase public void testAuditApplication_GetDataExtractors() { - AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST); + AuditApplication app = auditModelRegistry.getAuditApplicationByName(APPLICATION_TEST); assertNotNull(app); Map extractors = app.getDataExtractors("/blah"); @@ -173,7 +196,7 @@ public class AuditBootstrapTest extends TestCase public void testAuditApplication_GetDataGenerators() { - AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST); + AuditApplication app = auditModelRegistry.getAuditApplicationByName(APPLICATION_TEST); assertNotNull(app); Map generators = app.getDataGenerators("/blah"); diff --git a/source/java/org/alfresco/repo/audit/AuditComponent.java b/source/java/org/alfresco/repo/audit/AuditComponent.java index 37bf3f31bb..435c6fd78d 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponent.java +++ b/source/java/org/alfresco/repo/audit/AuditComponent.java @@ -29,6 +29,7 @@ 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.AuditInfo; import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; @@ -153,17 +154,22 @@ public interface AuditComponent void resetDisabledPaths(String applicationName); /** - * Record a set of values against the given session. The map is a path - starting with '/' - * ({@link AuditApplication#AUDIT_PATH_SEPARATOR}), relative to the root path given when - * {@link #startAuditSession(String, String) starting the session}. All resulting path values - * (session root path + map entry paths) must have data recorder entries and be enabled for data to be recorded. + * 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. *

* This is a read-write method. Client code must wrap calls in the appropriate transactional wrappers. * - * @param applicationName the name of the application to log against * @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 @@ -175,7 +181,7 @@ public interface AuditComponent * * @since 3.2 */ - Map audit(String applicationName, String rootPath, Map values); + Map recordAuditValues(String rootPath, Map values); /** * Get the audit entries that match the given criteria. diff --git a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java index e51bb8ee1d..1a41e81664 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java @@ -64,6 +64,7 @@ import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PathMapper; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -782,7 +783,7 @@ public class AuditComponentImpl implements AuditComponent { this.propertyValueDAO = propertyValueDAO; } - + /** * {@inheritDoc} * @since 3.2 @@ -790,13 +791,9 @@ public class AuditComponentImpl implements AuditComponent public void deleteAuditEntries(String applicationName, Long fromTime, Long toTime) { ParameterCheck.mandatory("applicationName", applicationName); + AlfrescoTransactionSupport.checkTransactionReadState(true); - if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_NONE) - { - throw new IllegalStateException("Auditing requires a read transaction."); - } - - AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); if (application == null) { if (logger.isDebugEnabled()) @@ -845,13 +842,9 @@ public class AuditComponentImpl implements AuditComponent { ParameterCheck.mandatory("applicationName", applicationName); ParameterCheck.mandatory("path", path); + AlfrescoTransactionSupport.checkTransactionReadState(false); - if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_NONE) - { - throw new IllegalStateException("Auditing requires a read transaction."); - } - - AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); if (application == null) { if (logger.isDebugEnabled()) @@ -895,13 +888,9 @@ public class AuditComponentImpl implements AuditComponent { ParameterCheck.mandatory("applicationName", applicationName); ParameterCheck.mandatory("path", path); + AlfrescoTransactionSupport.checkTransactionReadState(true); - if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) - { - throw new IllegalStateException("Auditing requires a read-write transaction."); - } - - AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); if (application == null) { if (logger.isDebugEnabled()) @@ -951,13 +940,9 @@ public class AuditComponentImpl implements AuditComponent { ParameterCheck.mandatory("applicationName", applicationName); ParameterCheck.mandatory("path", path); + AlfrescoTransactionSupport.checkTransactionReadState(true); - if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) - { - throw new IllegalStateException("Auditing requires a read-write transaction."); - } - - AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); if (application == null) { if (logger.isDebugEnabled()) @@ -1028,12 +1013,9 @@ public class AuditComponentImpl implements AuditComponent public void resetDisabledPaths(String applicationName) { ParameterCheck.mandatory("applicationName", applicationName); + AlfrescoTransactionSupport.checkTransactionReadState(true); - if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) - { - throw new IllegalStateException("Auditing requires a read-write transaction."); - } - AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + AuditApplication application = auditModelRegistry.getAuditApplicationByName(applicationName); if (application == null) { if (logger.isDebugEnabled()) @@ -1055,56 +1037,16 @@ public class AuditComponentImpl implements AuditComponent * {@inheritDoc} * @since 3.2 */ - public Map audit(String applicationName, String rootPath, Map values) + public Map recordAuditValues(String rootPath, Map values) { - ParameterCheck.mandatory("applicationName", applicationName); ParameterCheck.mandatory("rootPath", rootPath); - - if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) + AlfrescoTransactionSupport.checkTransactionReadState(true); + AuditApplication.checkPathFormat(rootPath); + + if (values == null || values.isEmpty()) { - throw new IllegalStateException("Auditing requires a read-write transaction."); - } - - if (values == null) - { - values = Collections.emptyMap(); - } - - // Get the application - AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); - if (application == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("No audit application named '" + applicationName + "' has been registered."); - } return Collections.emptyMap(); } - // Check the path against the application - application.checkPath(rootPath); - // Get the model ID for the application - Long applicationId = application.getApplicationId(); - if (applicationId == null) - { - throw new AuditException("No persisted instance exists for audit application: " + applicationName); - } - - // Get all disabled paths - Set disabledPaths = getDisabledPaths(application); - // Check if the root path has been disabled - // This is a fast check and will usually be activated if there are any exclusions - for (String disabledPath : disabledPaths) - { - if (rootPath.startsWith(disabledPath)) - { - logger.debug( - "Audit values root path has been excluded by disabled paths: \n" + - " Application: " + application + "\n" + - " Root Path: " + rootPath); - return Collections.emptyMap(); - } - } - // Build the key paths using the session root path Map pathedValues = new HashMap(values.size() * 2); for (Map.Entry entry : values.entrySet()) @@ -1114,8 +1056,82 @@ public class AuditComponentImpl implements AuditComponent pathedValues.put(path, entry.getValue()); } + // Translate the values map + PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); + Map mappedValues = pathMapper.convertMap(pathedValues); + + // 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(values.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 + 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( + AuditApplication application, + Set disabledPaths, + 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); + } + // Eliminate any paths that have been disabled - Iterator pathedValuesKeyIterator = pathedValues.keySet().iterator(); + Iterator pathedValuesKeyIterator = values.keySet().iterator(); while(pathedValuesKeyIterator.hasNext()) { String pathedValueKey = pathedValuesKeyIterator.next(); @@ -1129,25 +1145,24 @@ public class AuditComponentImpl implements AuditComponent } } // Check if there is anything left - if (pathedValues.size() == 0) + if (values.size() == 0) { if (logger.isDebugEnabled()) { logger.debug( "Audit values have all been excluded by disabled paths: \n" + " Application: " + application + "\n" + - " Root Path: " + rootPath + "\n" + " Values: " + values); } return Collections.emptyMap(); } // Generate data - Map generators = application.getDataGenerators(pathedValues.keySet()); + Map generators = application.getDataGenerators(values.keySet()); Map auditData = generateData(generators); // Now extract values - Map extractedData = extractData(application, pathedValues); + Map extractedData = extractData(application, values); // Combine extracted and generated values (extracted data takes precedence) auditData.putAll(extractedData); @@ -1168,9 +1183,9 @@ public class AuditComponentImpl implements AuditComponent { logger.debug( "New audit entry: \n" + - " Applicatoin ID: " + applicationId + "\n" + + " Application ID: " + applicationId + "\n" + " Entry ID: " + entryId + "\n" + - " Path Values: " + pathedValues + "\n" + + " Values: " + values + "\n" + " Audit Data: " + auditData); } return auditData; diff --git a/source/java/org/alfresco/repo/audit/AuditComponentTest.java b/source/java/org/alfresco/repo/audit/AuditComponentTest.java index 03383dc59d..601fd2d8af 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentTest.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentTest.java @@ -26,6 +26,7 @@ package org.alfresco.repo.audit; import java.io.Serializable; import java.net.URL; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -133,7 +134,7 @@ public class AuditComponentTest extends TestCase { try { - auditComponent.audit(APPLICATION_TEST, "/test", null); + auditComponent.recordAuditValues("/test", Collections.emptyMap()); fail("Should fail due to lack of a transaction."); } catch (IllegalStateException e) @@ -146,7 +147,7 @@ public class AuditComponentTest extends TestCase { try { - auditComponent.audit(APPLICATION_TEST, "test", null); + auditComponent.recordAuditValues("test", null); fail("Failed to detect illegal path"); } catch (AuditModelException e) @@ -155,14 +156,14 @@ public class AuditComponentTest extends TestCase } try { - auditComponent.audit(APPLICATION_TEST, "/test/", null); + auditComponent.recordAuditValues("/test/", null); fail("Failed to detect illegal path"); } catch (AuditModelException e) { // Expected } - Map auditedValues = auditComponent.audit("Bogus App", "/test", null); + Map auditedValues = auditComponent.recordAuditValues("/bogus", null); assertNotNull(auditedValues); assertTrue("Invalid application should not audit anything", auditedValues.isEmpty()); @@ -182,11 +183,11 @@ public class AuditComponentTest extends TestCase public Void execute() throws Throwable { Map values = new HashMap(13); - values.put("/test/1.1/2.1/3.1/4.1", new Long(41)); - values.put("/test/1.1/2.1/3.1/4.2", "42"); - values.put("/test/1.1/2.1/3.1/4.2", new Date()); + values.put("/2.1/3.1/4.1", new Long(41)); + values.put("/2.1/3.1/4.2", "42"); + values.put("/2.1/3.1/4.2", new Date()); - auditComponent.audit(APPLICATION_TEST, "/test/1.1", values); + auditComponent.recordAuditValues("/test/1.1", values); return null; } @@ -224,7 +225,7 @@ public class AuditComponentTest extends TestCase { String actionPath = AuditApplication.buildPath("actions-test/actions"); - return auditComponent.audit(APPLICATION_ACTIONS_TEST, actionPath, adjustedValues); + return auditComponent.recordAuditValues(actionPath, adjustedValues); } }; return transactionService.getRetryingTransactionHelper().doInTransaction(auditCallback); @@ -235,47 +236,17 @@ public class AuditComponentTest extends TestCase */ private void checkAuditMaps(Map result, Map expected) { - Map copyResult = new HashMap(result); - - boolean failure = false; - - StringBuilder sb = new StringBuilder(1024); - sb.append("\nValues that don't match the expected values: "); - for (Map.Entry entry : expected.entrySet()) + String failure = EqualsHelper.getMapDifferenceReport(result, expected); + if (failure != null) { - String key = entry.getKey(); - Serializable expectedValue = entry.getValue(); - Serializable resultValue = result.get(key); - if (!EqualsHelper.nullSafeEquals(resultValue, expectedValue)) - { - sb.append("\n") - .append(" Key: ").append(key).append("\n") - .append(" Result: ").append(resultValue).append("\n") - .append(" Expected: ").append(expectedValue); - failure = true; - } - copyResult.remove(key); - } - sb.append("\nValues that are present but should not be: "); - for (Map.Entry entry : copyResult.entrySet()) - { - String key = entry.getKey(); - Serializable resultValue = entry.getValue(); - sb.append("\n") - .append(" Key: ").append(key).append("\n") - .append(" Result: ").append(resultValue); - failure = true; - } - if (failure) - { - fail(sb.toString()); + fail(failure); } } /** * Test auditing of something resembling real-world data */ - public void testAudit_Action01() throws Exception + private void auditAction01(String actionName) throws Exception { Serializable valueA = new Date(); Serializable valueB = "BBB-value-here"; @@ -290,7 +261,7 @@ public class AuditComponentTest extends TestCase parameters.put("b", valueB); parameters.put("c", valueC); - Map result = auditTestAction("action-01", nodeRef, parameters); + Map result = auditTestAction(actionName, nodeRef, parameters); Map expected = new HashMap(); expected.put("/actions-test/actions/user", AuthenticationUtil.getFullyAuthenticatedUser()); @@ -303,6 +274,22 @@ public class AuditComponentTest extends TestCase checkAuditMaps(result, expected); } + /** + * Test auditing of something resembling real-world data + */ + public void testAudit_Action01() throws Exception + { + auditAction01("action-01"); + } + + /** + * Test auditing of something resembling real-world data + */ + public void testAudit_Action01Mapped() throws Exception + { + auditAction01("action-01-mapped"); + } + public void testQuery_Action01() throws Exception { final Long beforeTime = new Long(System.currentTimeMillis()); diff --git a/source/java/org/alfresco/repo/audit/model/AuditApplication.java b/source/java/org/alfresco/repo/audit/model/AuditApplication.java index 99a5f6493c..2ec374cb0a 100644 --- a/source/java/org/alfresco/repo/audit/model/AuditApplication.java +++ b/source/java/org/alfresco/repo/audit/model/AuditApplication.java @@ -174,6 +174,7 @@ public class AuditApplication */ public void checkPath(String path) { + checkPathFormat(path); if (path == null || path.length() == 0) { generateException(path, "Empty or null audit path"); @@ -192,6 +193,26 @@ public class AuditApplication } } + /** + * Helper method to check that a path is correct for this application instance + * + * @param path the path in format /app-key/x/y/z + * @throws AuditModelException if the path is invalid + * + * @see #AUDIT_PATH_REGEX + */ + public static void checkPathFormat(String path) + { + if (path == null || path.length() == 0) + { + throw new AuditModelException("Empty or null audit path"); + } + else if (!path.matches(AUDIT_PATH_REGEX)) + { + throw new AuditModelException("An audit must match regular expression: " + AUDIT_PATH_REGEX); + } + } + /** * Compile a path or part of a path into a single string which always starts with the * {@link #AUDIT_PATH_SEPARATOR}. This can be a relative path so need not always start with @@ -237,6 +258,33 @@ public class AuditApplication return path; } + /** + * @param path the audit path for form /abc/def + * @return the root key of form abc + * + * @see #AUDIT_ROOT_KEY_REGEX + */ + public static String getRootKey(String path) + { + if (!path.startsWith(AUDIT_PATH_SEPARATOR)) + { + throw new AuditModelException( + "The path must start with the path separator '" + AUDIT_PATH_SEPARATOR + "'"); + } + String rootPath; + int index = path.indexOf(AUDIT_PATH_SEPARATOR, 1); + if (index > 0) + { + rootPath = path.substring(1, index); + } + else + { + rootPath = path.substring(1); + } + // Done + return rootPath; + } + /** * Get all data extractors applicable to a given path and scope. * diff --git a/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java b/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java index f01888ed88..207bb8c386 100644 --- a/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java +++ b/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java @@ -54,11 +54,14 @@ import org.alfresco.repo.audit.model._3.Audit; import org.alfresco.repo.audit.model._3.DataExtractors; import org.alfresco.repo.audit.model._3.DataGenerators; import org.alfresco.repo.audit.model._3.ObjectFactory; +import org.alfresco.repo.audit.model._3.PathMap; +import org.alfresco.repo.audit.model._3.PathMappings; import org.alfresco.repo.domain.audit.AuditDAO; import org.alfresco.repo.domain.audit.AuditDAO.AuditApplicationInfo; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PathMapper; import org.alfresco.util.PropertyCheck; import org.alfresco.util.registry.NamedObjectRegistry; import org.apache.commons.logging.Log; @@ -92,6 +95,14 @@ public class AuditModelRegistry private final Set auditModelUrls; private final List auditModels; + /** + * Used to lookup path translations + */ + private PathMapper auditPathMapper; + /** + * Used to lookup the audit application java hierarchy + */ + private final Map auditApplicationsByKey; /** * Used to lookup the audit application java hierarchy */ @@ -110,6 +121,8 @@ public class AuditModelRegistry auditModelUrls = new HashSet(7); auditModels = new ArrayList(7); + auditPathMapper = new PathMapper(); + auditApplicationsByKey = new HashMap(7); auditApplicationsByName = new HashMap(7); } @@ -204,6 +217,7 @@ public class AuditModelRegistry private void clearCaches() { auditModels.clear(); + auditApplicationsByKey.clear(); auditApplicationsByName.clear(); } @@ -227,11 +241,11 @@ public class AuditModelRegistry Set auditModelUrlsInner = new HashSet(auditModelUrls); for (URL auditModelUrl : auditModelUrlsInner) { - Audit audit = AuditModelRegistry.unmarshallModel(auditModelUrl); - // That worked, so now get an input stream and write the model - Long auditModelId = auditDAO.getOrCreateAuditModel(auditModelUrl).getFirst(); try { + Audit audit = AuditModelRegistry.unmarshallModel(auditModelUrl); + // That worked, so now get an input stream and write the model + Long auditModelId = auditDAO.getOrCreateAuditModel(auditModelUrl).getFirst(); // Now cache it (eagerly) cacheAuditElements(auditModelId, audit); } @@ -258,7 +272,9 @@ public class AuditModelRegistry clearCaches(); try { + auditPathMapper = new PathMapper(); transactionService.getRetryingTransactionHelper().doInTransaction(loadModelsCallback, false, true); + auditPathMapper.lock(); } finally { @@ -266,13 +282,32 @@ public class AuditModelRegistry } } + /** + * Get the application model for the given root key (as defined on the application) + * + * @param key the key defined on the application + * @return the java model (null if not found) + */ + public AuditApplication getAuditApplicationByKey(String key) + { + readLock.lock(); + try + { + return auditApplicationsByKey.get(key); + } + finally + { + readLock.unlock(); + } + } + /** * Get the application model for the given application name * * @param applicationName the name of the audited application * @return the java model (null if not found) */ - public AuditApplication getAuditApplication(String applicationName) + public AuditApplication getAuditApplicationByName(String applicationName) { readLock.lock(); try @@ -285,6 +320,15 @@ public class AuditModelRegistry } } + /** + * Get the + * @return + */ + public PathMapper getAuditPathMapper() + { + return auditPathMapper; + } + /** * Unmarshalls the Audit model from the URL. * @@ -331,7 +375,7 @@ public class AuditModelRegistry " Location: Line " + locator.getLineNumber() + " column " + locator.getColumnNumber() + "\n" + " Error: " + ve.getMessage()); } - return true; + return false; } }); } @@ -486,10 +530,18 @@ public class AuditModelRegistry List applications = audit.getApplication(); for (Application application : applications) { + String key = application.getKey(); + if (auditApplicationsByKey.containsKey(key)) + { + throw new AuditModelException( + "Audit application key '" + key + "' is used by: " + auditApplicationsByKey.get(key)); + } + String name = application.getName(); if (auditApplicationsByName.containsKey(name)) { - throw new AuditModelException("Audit application '" + name + "' has already been defined."); + throw new AuditModelException( + "Audit application '" + name + "' is used by: " + auditApplicationsByName.get(name)); } // Get the ID of the application @@ -511,8 +563,29 @@ public class AuditModelRegistry appInfo.getId(), appInfo.getDisabledPathsId()); auditApplicationsByName.put(name, wrapperApp); + auditApplicationsByKey.put(key, wrapperApp); } + // Pull out all the audit path maps + buildAuditPathMap(audit); // Store the model itself auditModels.add(audit); } + + /** + * Construct the reverse lookup maps for quick conversion of data to target maps + */ + private void buildAuditPathMap(Audit audit) + { + PathMappings pathMappings = audit.getPathMappings(); + if (pathMappings == null) + { + pathMappings = objectFactory.createPathMappings(); + } + for (PathMap pathMap : pathMappings.getPathMap()) + { + String sourcePath = pathMap.getSource(); + String targetPath = pathMap.getTarget(); + auditPathMapper.addPathMap(sourcePath, targetPath); + } + } } diff --git a/source/java/org/alfresco/repo/audit/model/_3/Application.java b/source/java/org/alfresco/repo/audit/model/_3/Application.java index 2237f46be8..4dc765b24c 100644 --- a/source/java/org/alfresco/repo/audit/model/_3/Application.java +++ b/source/java/org/alfresco/repo/audit/model/_3/Application.java @@ -1,9 +1,12 @@ package org.alfresco.repo.audit.model._3; +import java.util.ArrayList; +import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; @@ -16,6 +19,9 @@ import javax.xml.bind.annotation.XmlType; * <complexType name="Application"> * <complexContent> * <extension base="{http://www.alfresco.org/repo/audit/model/3.2}AuditPath"> + * <sequence> + * <element name="PathMappings" type="{http://www.alfresco.org/repo/audit/model/3.2}PathMappings" maxOccurs="unbounded" minOccurs="0"/> + * </sequence> * <attribute name="name" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}NameAttribute" /> * </extension> * </complexContent> @@ -25,14 +31,47 @@ import javax.xml.bind.annotation.XmlType; * */ @XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "Application") +@XmlType(name = "Application", propOrder = { + "pathMappings" +}) public class Application extends AuditPath { + @XmlElement(name = "PathMappings") + protected List pathMappings; @XmlAttribute(required = true) protected String name; + /** + * Gets the value of the pathMappings property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the pathMappings property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getPathMappings().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link PathMappings } + * + * + */ + public List getPathMappings() { + if (pathMappings == null) { + pathMappings = new ArrayList(); + } + return this.pathMappings; + } + /** * Gets the value of the name property. * diff --git a/source/java/org/alfresco/repo/audit/model/_3/Audit.java b/source/java/org/alfresco/repo/audit/model/_3/Audit.java index 4d45ea6ba3..2b97166c07 100644 --- a/source/java/org/alfresco/repo/audit/model/_3/Audit.java +++ b/source/java/org/alfresco/repo/audit/model/_3/Audit.java @@ -21,6 +21,7 @@ import javax.xml.bind.annotation.XmlType; * <sequence> * <element name="DataExtractors" type="{http://www.alfresco.org/repo/audit/model/3.2}DataExtractors" minOccurs="0"/> * <element name="DataGenerators" type="{http://www.alfresco.org/repo/audit/model/3.2}DataGenerators" minOccurs="0"/> + * <element name="PathMappings" type="{http://www.alfresco.org/repo/audit/model/3.2}PathMappings" minOccurs="0"/> * <element name="Application" type="{http://www.alfresco.org/repo/audit/model/3.2}Application" maxOccurs="unbounded" minOccurs="0"/> * </sequence> * </restriction> @@ -34,6 +35,7 @@ import javax.xml.bind.annotation.XmlType; @XmlType(name = "Audit", propOrder = { "dataExtractors", "dataGenerators", + "pathMappings", "application" }) public class Audit { @@ -42,6 +44,8 @@ public class Audit { protected DataExtractors dataExtractors; @XmlElement(name = "DataGenerators") protected DataGenerators dataGenerators; + @XmlElement(name = "PathMappings") + protected PathMappings pathMappings; @XmlElement(name = "Application") protected List application; @@ -93,6 +97,30 @@ public class Audit { this.dataGenerators = value; } + /** + * Gets the value of the pathMappings property. + * + * @return + * possible object is + * {@link PathMappings } + * + */ + public PathMappings getPathMappings() { + return pathMappings; + } + + /** + * Sets the value of the pathMappings property. + * + * @param value + * allowed object is + * {@link PathMappings } + * + */ + public void setPathMappings(PathMappings value) { + this.pathMappings = value; + } + /** * Gets the value of the application property. * diff --git a/source/java/org/alfresco/repo/audit/model/_3/ObjectFactory.java b/source/java/org/alfresco/repo/audit/model/_3/ObjectFactory.java index 72c534043c..e5d10ed115 100644 --- a/source/java/org/alfresco/repo/audit/model/_3/ObjectFactory.java +++ b/source/java/org/alfresco/repo/audit/model/_3/ObjectFactory.java @@ -33,14 +33,6 @@ public class ObjectFactory { public ObjectFactory() { } - /** - * Create an instance of {@link RecordValue } - * - */ - public RecordValue createRecordValue() { - return new RecordValue(); - } - /** * Create an instance of {@link DataExtractor } * @@ -57,6 +49,14 @@ public class ObjectFactory { return new Audit(); } + /** + * Create an instance of {@link DataExtractors } + * + */ + public DataExtractors createDataExtractors() { + return new DataExtractors(); + } + /** * Create an instance of {@link AuditPath } * @@ -66,19 +66,11 @@ public class ObjectFactory { } /** - * Create an instance of {@link DataGenerator } + * Create an instance of {@link PathMap } * */ - public DataGenerator createDataGenerator() { - return new DataGenerator(); - } - - /** - * Create an instance of {@link DataGenerators } - * - */ - public DataGenerators createDataGenerators() { - return new DataGenerators(); + public PathMap createPathMap() { + return new PathMap(); } /** @@ -89,6 +81,22 @@ public class ObjectFactory { return new KeyedAuditDefinition(); } + /** + * Create an instance of {@link PathMappings } + * + */ + public PathMappings createPathMappings() { + return new PathMappings(); + } + + /** + * Create an instance of {@link DataGenerator } + * + */ + public DataGenerator createDataGenerator() { + return new DataGenerator(); + } + /** * Create an instance of {@link Application } * @@ -97,6 +105,14 @@ public class ObjectFactory { return new Application(); } + /** + * Create an instance of {@link DataGenerators } + * + */ + public DataGenerators createDataGenerators() { + return new DataGenerators(); + } + /** * Create an instance of {@link GenerateValue } * @@ -106,11 +122,11 @@ public class ObjectFactory { } /** - * Create an instance of {@link DataExtractors } + * Create an instance of {@link RecordValue } * */ - public DataExtractors createDataExtractors() { - return new DataExtractors(); + public RecordValue createRecordValue() { + return new RecordValue(); } /** diff --git a/source/java/org/alfresco/repo/audit/model/_3/PathMap.java b/source/java/org/alfresco/repo/audit/model/_3/PathMap.java new file mode 100644 index 0000000000..483dcb5928 --- /dev/null +++ b/source/java/org/alfresco/repo/audit/model/_3/PathMap.java @@ -0,0 +1,85 @@ + +package org.alfresco.repo.audit.model._3; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for PathMap complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="PathMap">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <attribute name="source" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}PathAttribute" />
+ *       <attribute name="target" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}PathAttribute" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PathMap") +public class PathMap { + + @XmlAttribute(required = true) + protected String source; + @XmlAttribute(required = true) + protected String target; + + /** + * Gets the value of the source property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getSource() { + return source; + } + + /** + * Sets the value of the source property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setSource(String value) { + this.source = value; + } + + /** + * Gets the value of the target property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getTarget() { + return target; + } + + /** + * Sets the value of the target property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setTarget(String value) { + this.target = value; + } + +} diff --git a/source/java/org/alfresco/repo/audit/model/_3/PathMappings.java b/source/java/org/alfresco/repo/audit/model/_3/PathMappings.java new file mode 100644 index 0000000000..155b22eee0 --- /dev/null +++ b/source/java/org/alfresco/repo/audit/model/_3/PathMappings.java @@ -0,0 +1,69 @@ + +package org.alfresco.repo.audit.model._3; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for PathMappings complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="PathMappings">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="PathMap" type="{http://www.alfresco.org/repo/audit/model/3.2}PathMap" maxOccurs="unbounded" minOccurs="0"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PathMappings", propOrder = { + "pathMap" +}) +public class PathMappings { + + @XmlElement(name = "PathMap") + protected List pathMap; + + /** + * Gets the value of the pathMap property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the pathMap property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getPathMap().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link PathMap } + * + * + */ + public List getPathMap() { + if (pathMap == null) { + pathMap = new ArrayList(); + } + return this.pathMap; + } + +} diff --git a/source/test-resources/alfresco/audit/alfresco-audit-test.xml b/source/test-resources/alfresco/audit/alfresco-audit-test.xml index ac19fc5be1..4fee4a1ce7 100644 --- a/source/test-resources/alfresco/audit/alfresco-audit-test.xml +++ b/source/test-resources/alfresco/audit/alfresco-audit-test.xml @@ -19,6 +19,13 @@ + + + + + + +