Added 'enableAuditPath' and 'disableAuditPath'

- Various tests to see that the recorded data is changed
 - disabledPaths rely entirely on the property caching for fast retrieval


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@16271 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-09-15 10:06:31 +00:00
parent b0aac65e9a
commit 2c33287ea3
9 changed files with 421 additions and 70 deletions

View File

@@ -38,6 +38,7 @@
</property>
<!-- V3.2 specific -->
<property name="auditModelRegistry" ref="auditModel.modelRegistry"/>
<property name="propertyValueDAO" ref="propertyValueDAO"/>
</bean>
<!-- Public service idntifier -->

View File

@@ -121,7 +121,9 @@ public class AuditBootstrapTest extends TestCase
public void testGetApplicationId()
{
Long appId = auditModelRegistry.getAuditApplicationId(APPLICATION_TEST);
AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST);
assertNotNull(app);
Long appId = app.getApplicationId();
assertNotNull("No audit application ID for " + APPLICATION_TEST, appId);
}

View File

@@ -84,6 +84,50 @@ public interface AuditComponent
* V3.2 from here on. Put all fixes to the older audit code before this point, please.
*/
/**
* Enable auditing (if it is not already enabled) for all paths that contain the given path.
* The path is the path as originally logged (see {@link #audit(String, String, Map)}) and
* not the path that the generated data may contain - although this would be similarly
* enabled.
* <p>
* If the enabled
*
* @param applicationName the name of the application being logged to
* @param path the audit path to enable auditing on
*
* @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 (see {@link #audit(String, String, Map)}) and
* not the path that the generated data may contain - although this would be similarly
* disabled.
* <p>
* If the path is <b>/x/y</b> then any data paths that start with <b>/x/y</b> will be stripped
* out <u>before</u> 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 enable auditing on
*
* @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) disabling}.
*
* @param applicationName the name of the application
*
* @since 3.2
*/
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
@@ -118,6 +162,8 @@ public interface AuditComponent
* @param from the start search time (<tt>null</tt> to start at the beginning)
* @param to the end search time (<tt>null</tt> for no limit)
* @param maxResults the maximum number of results to retrieve (zero or negative to ignore)
*
* @since 3.2
*/
void auditQuery(
AuditQueryCallback callback,
@@ -135,6 +181,8 @@ public interface AuditComponent
* @param searchKey the audit key path that must exist (<tt>null</tt> to ignore)
* @param searchString an audit value string that must exist (<tt>null</tt> to ignore)
* @param maxResults the maximum number of results to retrieve (zero or negative to ignore)
*
* @since 3.2
*/
void auditQuery(
AuditQueryCallback callback,

View File

@@ -31,8 +31,11 @@ import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Date;
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;
@@ -42,6 +45,7 @@ import org.alfresco.repo.audit.model.AuditEntry;
import org.alfresco.repo.audit.model.AuditModelRegistry;
import org.alfresco.repo.audit.model.TrueFalseUnset;
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.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
@@ -759,6 +763,7 @@ public class AuditComponentImpl implements AuditComponent
*/
private AuditModelRegistry auditModelRegistry;
private PropertyValueDAO propertyValueDAO;
/**
* Set the registry holding the audit models
@@ -769,6 +774,200 @@ public class AuditComponentImpl implements AuditComponent
this.auditModelRegistry = auditModelRegistry;
}
/**
* Set the DAO for manipulating property values
* @since 3.2
*/
public void setPropertyValueDAO(PropertyValueDAO propertyValueDAO)
{
this.propertyValueDAO = propertyValueDAO;
}
/**
* @param application the audit application object
* @return Returns a copy of the set of disabled paths associated with the application
*/
@SuppressWarnings("unchecked")
private Set<String> getDisabledPaths(AuditApplication application)
{
try
{
Long disabledPathsId = application.getDisabledPathsId();
Set<String> disabledPaths = (Set<String>) propertyValueDAO.getPropertyById(disabledPathsId);
return new HashSet<String>(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 void enableAudit(String applicationName, String path)
{
ParameterCheck.mandatory("applicationName", applicationName);
ParameterCheck.mandatory("path", path);
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
{
throw new IllegalStateException("Auditing requires a read-write transaction.");
}
AuditApplication application = auditModelRegistry.getAuditApplication(applicationName);
if (application == null)
{
if (logger.isDebugEnabled())
{
logger.debug("No audit application named '" + applicationName + "' has been registered.");
}
return;
}
// Check the path against the application
application.checkPath(path);
Long disabledPathsId = application.getDisabledPathsId();
Set<String> disabledPaths = getDisabledPaths(application);
// Remove any paths that start with the given path
boolean changed = false;
Iterator<String> 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);
ParameterCheck.mandatory("path", path);
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
{
throw new IllegalStateException("Auditing requires a read-write transaction.");
}
AuditApplication application = auditModelRegistry.getAuditApplication(applicationName);
if (application == null)
{
if (logger.isDebugEnabled())
{
logger.debug("No audit application named '" + applicationName + "' has been registered.");
}
return;
}
// Check the path against the application
application.checkPath(path);
Long disabledPathsId = application.getDisabledPathsId();
Set<String> 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<String> 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);
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
{
throw new IllegalStateException("Auditing requires a read-write transaction.");
}
AuditApplication application = auditModelRegistry.getAuditApplication(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);
}
}
/**
* {@inheritDoc}
* @since 3.2
@@ -801,13 +1000,27 @@ public class AuditComponentImpl implements AuditComponent
// Check the path against the application
application.checkPath(rootPath);
// Get the model ID for the application
Long applicationId = auditModelRegistry.getAuditApplicationId(applicationName);
Long applicationId = application.getApplicationId();
if (applicationId == null)
{
throw new AuditException("No persisted instance exists for audit application: " + applicationName);
}
// TODO: Check if the root path is enabled or not
// Get all disabled paths
Set<String> 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<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
@@ -818,6 +1031,34 @@ public class AuditComponentImpl implements AuditComponent
pathedValues.put(path, entry.getValue());
}
// Eliminate any paths that have been disabled
Iterator<String> pathedValuesKeyIterator = pathedValues.keySet().iterator();
while(pathedValuesKeyIterator.hasNext())
{
String pathedValueKey = pathedValuesKeyIterator.next();
for (String disabledPath : disabledPaths)
{
if (pathedValueKey.startsWith(disabledPath))
{
// The pathed value is excluded
pathedValuesKeyIterator.remove();
}
}
}
// Check if there is anything left
if (pathedValues.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<String, DataGenerator> generators = application.getDataGenerators(pathedValues.keySet());
Map<String, Serializable> auditData = generateData(generators);

View File

@@ -105,6 +105,17 @@ public class AuditComponentTest extends TestCase
// Authenticate
user = "User-" + getName();
AuthenticationUtil.setFullyAuthenticatedUser(user);
final RetryingTransactionCallback<Void> resetDisabledPathsCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
auditComponent.resetDisabledPaths(APPLICATION_TEST);
auditComponent.resetDisabledPaths(APPLICATION_ACTIONS_TEST);
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(resetDisabledPathsCallback);
}
@Override
@@ -368,4 +379,74 @@ public class AuditComponentTest extends TestCase
logger.debug(sb.toString());
}
/**
* Test disabling of audit using audit paths
*/
public void testAudit_EnableDisableAuditPaths() throws Exception
{
Serializable valueA = new Date();
Serializable valueB = "BBB-value-here";
Serializable valueC = new Float(16.0F);
// Get a noderef
final Map<String, Serializable> parameters = new HashMap<String, Serializable>(13);
parameters.put("A", valueA);
parameters.put("B", valueB);
parameters.put("C", valueC);
// lowercase versions are not in the config
parameters.put("a", valueA);
parameters.put("b", valueB);
parameters.put("c", valueC);
Map<String, Serializable> result = auditTestAction("action-01", nodeRef, parameters);
final Map<String, Serializable> expected = new HashMap<String, Serializable>();
expected.put("/actions-test/actions/user", AuthenticationUtil.getFullyAuthenticatedUser());
expected.put("/actions-test/actions/context-node/noderef", nodeRef);
expected.put("/actions-test/actions/action-01/params/A/value", valueA);
expected.put("/actions-test/actions/action-01/params/B/value", valueB);
expected.put("/actions-test/actions/action-01/params/C/value", valueC);
// Check
checkAuditMaps(result, expected);
// Good. Now disable a path and recheck
RetryingTransactionCallback<Void> disableAuditCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
Map<String, Serializable> expectedInner = new HashMap<String, Serializable>(expected);
auditComponent.disableAudit(APPLICATION_ACTIONS_TEST, "/actions-test/actions/action-01/params/A");
expectedInner.remove("/actions-test/actions/action-01/params/A/value");
Map<String, Serializable> result = auditTestAction("action-01", nodeRef, parameters);
checkAuditMaps(result, expectedInner);
auditComponent.disableAudit(APPLICATION_ACTIONS_TEST, "/actions-test/actions/action-01/params/B");
expectedInner.remove("/actions-test/actions/action-01/params/B/value");
result = auditTestAction("action-01", nodeRef, parameters);
checkAuditMaps(result, expectedInner);
auditComponent.disableAudit(APPLICATION_ACTIONS_TEST, "/actions-test");
expectedInner.clear();
result = auditTestAction("action-01", nodeRef, parameters);
checkAuditMaps(result, expectedInner);
// Enabling something lower down should make no difference
auditComponent.enableAudit(APPLICATION_ACTIONS_TEST, "/actions-test/actions/action-01/params/B");
expectedInner.clear();
result = auditTestAction("action-01", nodeRef, parameters);
checkAuditMaps(result, expectedInner);
// Enabling the root should give back everything
auditComponent.enableAudit(APPLICATION_ACTIONS_TEST, "/actions-test");
expectedInner = new HashMap<String, Serializable>(expected);
result = auditTestAction("action-01", nodeRef, parameters);
checkAuditMaps(result, expectedInner);
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(disableAuditCallback, false);
}
}

View File

@@ -62,6 +62,8 @@ public class AuditApplication
private final Map<String, DataGenerator> dataGeneratorsByName;
@SuppressWarnings("unused")
private final Application application;
private final Long applicationId;
private final Long disabledPathsId;
/** Derived expaned map for fast lookup */
private Map<String, Map<String, DataExtractor>> dataExtractors = new HashMap<String, Map<String, DataExtractor>>(11);
@@ -76,7 +78,9 @@ public class AuditApplication
/* package */ AuditApplication(
Map<String, DataExtractor> dataExtractorsByName,
Map<String, DataGenerator> dataGeneratorsByName,
Application application)
Application application,
Long applicationId,
Long disabledPathsId)
{
this.dataExtractorsByName = dataExtractorsByName;
this.dataGeneratorsByName = dataGeneratorsByName;
@@ -84,6 +88,8 @@ public class AuditApplication
this.applicationName = application.getName();
this.applicationKey = application.getKey();
this.applicationId = applicationId;
this.disabledPathsId = disabledPathsId;
buildAuditPaths(application);
}
@@ -118,6 +124,8 @@ public class AuditApplication
StringBuilder sb = new StringBuilder(56);
sb.append("AuditApplication")
.append("[ name=").append(applicationName)
.append(", id=").append(applicationId)
.append(", disabledPathsId=").append(disabledPathsId)
.append("]");
return sb.toString();
}
@@ -138,6 +146,24 @@ public class AuditApplication
return applicationKey;
}
/**
* Get the database ID for this application
*/
public Long getApplicationId()
{
return applicationId;
}
/**
* Get the property representing the set of disabled paths for the application
*
* @return Returns an ID <code>Set<String></code> of disabled paths
*/
public Long getDisabledPathsId()
{
return disabledPathsId;
}
/**
* Helper method to check that a path is correct for this application instance
*

View File

@@ -96,14 +96,6 @@ public class AuditModelRegistry
* Used to lookup the audit application java hierarchy
*/
private final Map<String, AuditApplication> auditApplicationsByName;
/**
* Used to lookup a reference to the application
*/
private final Map<String, Long> auditApplicationIdsByApplicationsName;
/**
* Used to lookup application disabled paths
*/
private final Map<String, Set<String>> auditDisabledPathsByApplicationsName;
/**
* Default constructor
@@ -119,8 +111,6 @@ public class AuditModelRegistry
auditModelUrls = new HashSet<URL>(7);
auditModels = new ArrayList<Audit>(7);
auditApplicationsByName = new HashMap<String, AuditApplication>(7);
auditApplicationIdsByApplicationsName = new HashMap<String, Long>(7);
auditDisabledPathsByApplicationsName = new HashMap<String, Set<String>>(7);
}
/**
@@ -215,7 +205,6 @@ public class AuditModelRegistry
{
auditModels.clear();
auditApplicationsByName.clear();
auditApplicationIdsByApplicationsName.clear();
}
/**
@@ -223,7 +212,8 @@ public class AuditModelRegistry
* the audit models for later retrieval. Models are loaded from the locations given by the
* {@link #registerModel(URL) register} methods.
* <p/>
* Note, the models are loaded in a new transaction.
* Note, the models are loaded in a new transaction, so this method can be called by any code
* at any time.
*/
public void loadAuditModels()
{
@@ -268,7 +258,7 @@ public class AuditModelRegistry
clearCaches();
try
{
transactionService.getRetryingTransactionHelper().doInTransaction(loadModelsCallback);
transactionService.getRetryingTransactionHelper().doInTransaction(loadModelsCallback, false, true);
}
finally
{
@@ -276,25 +266,6 @@ public class AuditModelRegistry
}
}
/**
* Get the ID of the persisted audit application for the given application name
*
* @param applicationName the name of the audited application
* @return the unique ID of the persisted application (<tt>null</tt> if not found)
*/
public Long getAuditApplicationId(String applicationName)
{
readLock.lock();
try
{
return auditApplicationIdsByApplicationsName.get(applicationName);
}
finally
{
readLock.unlock();
}
}
/**
* Get the application model for the given application name
*
@@ -314,25 +285,6 @@ public class AuditModelRegistry
}
}
/**
* Get all disabled paths for the given application name
*
* @param applicationName the name of the audited application
* @return a set of paths for which logging is disabled
*/
public Set<String> getAuditDisabledPaths(String applicationName)
{
readLock.lock();
try
{
return auditDisabledPathsByApplicationsName.get(applicationName);
}
finally
{
readLock.unlock();
}
}
/**
* Unmarshalls the Audit model from the URL.
*
@@ -552,10 +504,13 @@ public class AuditModelRegistry
auditDAO.updateAuditApplicationModel(appInfo.getId(), auditModelId);
}
AuditApplication wrapperApp = new AuditApplication(dataExtractorsByName, dataGeneratorsByName, application);
AuditApplication wrapperApp = new AuditApplication(
dataExtractorsByName,
dataGeneratorsByName,
application,
appInfo.getId(),
appInfo.getDisabledPathsId());
auditApplicationsByName.put(name, wrapperApp);
auditApplicationIdsByApplicationsName.put(name, appInfo.getId());
auditDisabledPathsByApplicationsName.put(name, appInfo.getDisabledPaths());
}
// Store the model itself
auditModels.add(audit);

View File

@@ -209,7 +209,6 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
* alf_audit_application
*/
@SuppressWarnings("unchecked")
public AuditApplicationInfo getAuditApplication(String application)
{
AuditApplicationEntity entity = getAuditApplicationByName(application);
@@ -223,9 +222,7 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
appInfo.setId(entity.getId());
appInfo.setname(application);
appInfo.setModelId(entity.getAuditModelId());
// Resolve the disabled paths
Set<String> disabledPaths = (Set<String>) propertyValueDAO.getPropertyById(entity.getDisabledPathsId());
appInfo.setDisabledPaths(disabledPaths);
appInfo.setDisabledPathsId(entity.getDisabledPathsId());
// Done
if (logger.isDebugEnabled())
{
@@ -252,7 +249,7 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
appInfo.setId(entity.getId());
appInfo.setname(application);
appInfo.setModelId(modelId);
appInfo.setDisabledPaths(disabledPaths);
appInfo.setDisabledPathsId(disabledPathsId);
// Done
if (logger.isDebugEnabled())
{

View File

@@ -78,7 +78,7 @@ public interface AuditDAO
private Long id;
private String name;
private Long modelId;
private Set<String> disabledPaths;
private Long disabledPathsId;
@Override
public String toString()
@@ -88,7 +88,7 @@ public interface AuditDAO
.append("[ id=").append(id)
.append(", name=").append(name)
.append(", modelId=").append(modelId)
.append(", disabledPaths=").append(disabledPaths)
.append(", disabledPathsId=").append(disabledPathsId)
.append("]");
return sb.toString();
}
@@ -117,13 +117,13 @@ public interface AuditDAO
{
this.modelId = modelId;
}
public Set<String> getDisabledPaths()
public Long getDisabledPathsId()
{
return disabledPaths;
return disabledPathsId;
}
public void setDisabledPaths(Set<String> disabledPaths)
public void setDisabledPathsId(Long disabledPathsId)
{
this.disabledPaths = disabledPaths;
this.disabledPathsId = disabledPathsId;
}
}