mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
28236: ALF-8810: Removed trailing space from discussion.discussion_for Italian translation 28241: Incremented version revision for 3.4.4 28284: ALF-835 - WCM/AVM: copy (empty) folder into itself 28285: ALF-6863: More than one cifs device breaks the web UI (explorer) 28290: ALF-8840: user-*.atomentry.ftl 28291: ALF-6863: Continuation of fix by Arseny 28336: ALF-8768: Fixed typo in comment on wcm-bootstrap-context.xml 28363: Merged DEV to V3.4-BUG-FIX 28262: ALF-8847: WCM: OrphanReaper contention throws error after 39 retries. Checkin Comment: Use JobLockService to make sure that only one OrphanReaper job is working. Generate list of nodes that must be processed in OrphanReaper.doBatch() transaction. 28386: ALF-9100: Merged PATCHES/V3.4.1 to V3.4-BUG-FIX 28249: ALF-8946: Avoid one full table scan per batch in full reindex - Now each batch scans a single time sample, dynamically adjusted based on the number of transactions in the previous sample, always aiming for 1000 transactions per sample. 28394: Fixed ALF-9090: NPE during inter-cluster subsystem messaging - Bean ID is a List<String> and might not be recognized on receiving machine - Log warning when bean ID is not available (unsymmetrical configuration, perhaps?) 28396: Merged DEV to V3.4-BUG-FIX 28384: ALF-6150: Initial state lost when non-versionable document is saved for the first time Creation of new version of document before writing its content was added to - AbstractAlfrescoMethodHandler->putDocument (this method is used by Office 2003, 2007) - VtiIfHeaderAction->doPut (this method is used by Office 2007 and 2010 on Windows 7) Creation of new version was added twice to AbstractAlfrescoMethodHandler to avoid affecting initial version when transaction is committed. 28432: Merged DEV to V3.4-BUG-FIX 28431: ALF-8530: Pressing the info icon creates an unrecorded file in the ContentStore Use ContentService.getTempWriter() in BaseContentNode$TemplateContentData.getContentAsText() method. 28435: Merged DEV/TEMPORARY to V3.4-BUG-FIX 28428: ALF-9015: cm:modifier not updated when document is updated via CIFS In ContentDiskDriver.closeFile() added ContentModel.PROP_MODIFIER property update. 28436: ALF-8550: Number of http requests (currentThreadsBusy) increases when session times out during creation of webform - Corrected use of read and write locks 28465: Fix for ALF-8023 Share preview doesn't work if... fixed as outlined by Dmitry. 28478: Merged BRANCHES/DEV/ALAN/AUDIT to BRANCHES/DEV/V3.4-BUG-FIX: 28062-28477 (28062,28063,28080,28081,28302,28303,28334,28340,28464,28469,28477) ALF-8438 Need higher level audit of user actions git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28481 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
868 lines
31 KiB
Java
868 lines
31 KiB
Java
/*
|
|
* Copyright (C) 2005-2011 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
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.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.
|
|
* <p>
|
|
* To turn on logging of all <i>potentially auditable</i> data, turn on logging for:<br/>
|
|
* <strong>{@link #INBOUND_LOGGER org.alfresco.repo.audit.inbound}</strong>.
|
|
* <p/>
|
|
* 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;
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 3.2
|
|
*/
|
|
public int deleteAuditEntries(String applicationName, Long fromTime, Long toTime)
|
|
{
|
|
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 0;
|
|
}
|
|
|
|
Long applicationId = application.getApplicationId();
|
|
|
|
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 3.2
|
|
*/
|
|
@Override
|
|
public int deleteAuditEntries(List<Long> 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<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 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<String, AuditApplication> getAuditApplications()
|
|
{
|
|
return auditModelRegistry.getAuditApplications();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* <p/>
|
|
* Note that if DEBUG is on for the the {@link #INBOUND_LOGGER}, then <tt>true</tt>
|
|
* will always be returned.
|
|
*
|
|
* @since 3.2
|
|
*/
|
|
public boolean areAuditValuesRequired()
|
|
{
|
|
if (transactionService.isReadOnly())
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return
|
|
(loggerInbound.isDebugEnabled()) ||
|
|
(isAuditEnabled() && !auditModelRegistry.getAuditPathMapper().isEmpty());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 3.4
|
|
*/
|
|
@Override
|
|
public boolean areAuditValuesRequired(String path)
|
|
{
|
|
if (loggerInbound.isDebugEnabled())
|
|
{
|
|
return !transactionService.isReadOnly();
|
|
}
|
|
PathMapper pathMapper = auditModelRegistry.getAuditPathMapper();
|
|
Set<String> mappedPaths = pathMapper.getMappedPathsWithPartialMatch(path);
|
|
// for performance, fall through if no mapped paths so isReadyOnly is never called
|
|
return (mappedPaths.size() > 0 && !transactionService.isReadOnly());
|
|
}
|
|
|
|
/**
|
|
* {@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<String> 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<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);
|
|
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<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);
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 3.2
|
|
*/
|
|
public Map<String, Serializable> recordAuditValues(String rootPath, Map<String, Serializable> values)
|
|
{
|
|
ParameterCheck.mandatory("rootPath", rootPath);
|
|
AuditApplication.checkPathFormat(rootPath);
|
|
|
|
if (values == null || values.isEmpty() || !areAuditValuesRequired() || !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<String, Serializable> 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<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
|
|
for (Map.Entry<String, Serializable> 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<String, Serializable> 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<Map<String, Serializable>> callback =
|
|
new RetryingTransactionCallback<Map<String,Serializable>>()
|
|
{
|
|
public Map<String, Serializable> execute() throws Throwable
|
|
{
|
|
return recordAuditValuesImpl(mappedValues);
|
|
}
|
|
};
|
|
return transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true);
|
|
case TXN_READ_WRITE:
|
|
return recordAuditValuesImpl(mappedValues);
|
|
default:
|
|
throw new IllegalStateException("Unknown txn state: " + txnState);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 3.2
|
|
*/
|
|
public Map<String, Serializable> recordAuditValuesImpl(Map<String, Serializable> mappedValues)
|
|
{
|
|
// Group the values by root path
|
|
Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<String, Map<String,Serializable>>();
|
|
for (Map.Entry<String, Serializable> entry : mappedValues.entrySet())
|
|
{
|
|
String path = entry.getKey();
|
|
String rootKey = AuditApplication.getRootKey(path);
|
|
Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.get(rootKey);
|
|
if (rootKeyMappedValues == null)
|
|
{
|
|
rootKeyMappedValues = new HashMap<String, Serializable>(7);
|
|
mappedValuesByRootKey.put(rootKey, rootKeyMappedValues);
|
|
}
|
|
rootKeyMappedValues.put(path, entry.getValue());
|
|
}
|
|
|
|
Map<String, Serializable> allAuditedValues = new HashMap<String, Serializable>(mappedValues.size()*2+1);
|
|
// Now audit for each of the root keys
|
|
for (Map.Entry<String, Map<String, Serializable>> entry : mappedValuesByRootKey.entrySet())
|
|
{
|
|
String rootKey = entry.getKey();
|
|
Map<String, Serializable> 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<String> 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<String, Serializable> 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 <b>full paths</b>.
|
|
* @return Returns all values as audited
|
|
*/
|
|
private Map<String, Serializable> audit(
|
|
final AuditApplication application,
|
|
Set<String> disabledPaths,
|
|
final Map<String, Serializable> 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<String> generatorKeys = values.keySet();
|
|
// Eliminate any paths that have been disabled
|
|
Iterator<String> 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<String, DataGenerator> generators = application.getDataGenerators(generatorKeys);
|
|
Map<String, Serializable> auditData = generateData(generators);
|
|
|
|
// Now extract values
|
|
Map<String, Serializable> extractedData = AuthenticationUtil.runAs(new RunAsWork<Map<String, Serializable>>()
|
|
{
|
|
public Map<String, Serializable> doWork() throws Exception
|
|
{
|
|
return extractData(application, values);
|
|
}
|
|
}, AuthenticationUtil.getSystemUserName());
|
|
|
|
// Combine extracted and generated values (extracted data takes precedence)
|
|
auditData.putAll(extractedData);
|
|
|
|
// Time and username are intrinsic
|
|
long time = System.currentTimeMillis();
|
|
String username = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
|
|
Long entryId = null;
|
|
if (!auditData.isEmpty())
|
|
{
|
|
// Persist the values
|
|
entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData);
|
|
// Done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append(
|
|
"\nNew audit entry: \n" +
|
|
"\tApplication ID: " + applicationId + "\n" +
|
|
"\tEntry ID: " + entryId + "\n" +
|
|
"\tValues: " + "\n");
|
|
for (Map.Entry<String, Serializable> entry : values.entrySet())
|
|
{
|
|
sb.append("\t\t").append(entry).append("\n");
|
|
}
|
|
sb.append("\n\tAudit Data: \n");
|
|
for (Map.Entry<String, Serializable> 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<String, Serializable> 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<String, Serializable> extractData(
|
|
AuditApplication application,
|
|
Map<String, Serializable> values)
|
|
{
|
|
Map<String, Serializable> newData = new HashMap<String, Serializable>(values.size());
|
|
|
|
List<DataExtractorDefinition> 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<String, Serializable> entry : values.entrySet())
|
|
{
|
|
sb.append("\t\t").append(entry).append("\n");
|
|
}
|
|
sb.append("\n\tNew Data: \n");
|
|
for (Map.Entry<String, Serializable> 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<String, Serializable> generateData(Map<String, DataGenerator> generators)
|
|
{
|
|
Map<String, Serializable> newData = new HashMap<String, Serializable>(generators.size() + 5);
|
|
for (Map.Entry<String, DataGenerator> 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);
|
|
}
|
|
}
|