mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
- audit.useNewConfig=false is now the default setting and has a new meaning - In this mode, audit information is broadcast to BOTH the old and new audit configuration, meaning that e.g. you can enjoy new RM and CMIS auditing capabilities whilst still retaining compatibility with your old audit configuration - When audit.useNewConfig=true only the new config/alfresco/audit/*.xml configuration will be used exclusively. NOT config/auditConfig.xml. - Note that auditing is still switched off by default and must be switched on with audit.enabled=true - You can switch on and off individual applications in the new config using - audit.repository.enabled - audit.cmischangelog.enabled - audit.dod5015.enabled - Simple! git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@18885 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1484 lines
55 KiB
Java
1484 lines
55 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
* As a special exception to the terms and conditions of version 2.0 of
|
|
* the GPL, you may redistribute this Program in connection with Free/Libre
|
|
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
|
* FLOSS exception. You should have received a copy of the text describing
|
|
* the FLOSS exception, and it is also available here:
|
|
* http://www.alfresco.com/legal/licensing"
|
|
*/
|
|
package org.alfresco.repo.audit;
|
|
|
|
import java.io.Serializable;
|
|
import java.lang.reflect.Method;
|
|
import java.net.InetAddress;
|
|
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;
|
|
import org.alfresco.repo.audit.generator.DataGenerator;
|
|
import org.alfresco.repo.audit.model.AuditApplication;
|
|
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.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.Auditable;
|
|
import org.alfresco.service.NotAuditable;
|
|
import org.alfresco.service.PublicService;
|
|
import org.alfresco.service.cmr.audit.AuditInfo;
|
|
import org.alfresco.service.cmr.audit.AuditQueryParameters;
|
|
import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.Path;
|
|
import org.alfresco.service.cmr.repository.StoreRef;
|
|
import org.alfresco.service.cmr.search.SearchParameters;
|
|
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.PathMapper;
|
|
import org.aopalliance.intercept.MethodInvocation;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.aop.framework.ReflectiveMethodInvocation;
|
|
import org.springframework.extensions.surf.util.ParameterCheck;
|
|
|
|
/**
|
|
* The default audit component implementation. TODO: Implement before, after and exception filtering. At the moment
|
|
* these filters are ignored. TODO: Respect audit internal - at the moment audit internal is fixed to false.
|
|
* <p/>
|
|
* The V3.2 audit functionality is contained within the same component. When the newer audit
|
|
* implementation has been tested and approved, then older ones will be deprecated as necessary.
|
|
*
|
|
* @author Andy Hind
|
|
* @author Derek Hulley
|
|
*/
|
|
public class AuditComponentImpl implements AuditComponent
|
|
{
|
|
/**
|
|
* The application name to use for audit entries generated by method interception around public services.
|
|
*/
|
|
private static final String SYSTEM_APPLICATION = "SystemMethodInterceptor";
|
|
|
|
/**
|
|
* Logging
|
|
*/
|
|
private static Log logger = LogFactory.getLog(AuditComponentImpl.class);
|
|
|
|
/**
|
|
* Suspend resume auditing
|
|
*/
|
|
private static ThreadLocal<Boolean> auditFlag = new ThreadLocal<Boolean>();
|
|
|
|
/**
|
|
* IOC
|
|
*/
|
|
private PublicServiceIdentifier publicServiceIdentifier;
|
|
|
|
private AuditConfiguration auditConfiguration;
|
|
|
|
private AuditDAO auditDAO;
|
|
|
|
private TransactionService transactionService;
|
|
|
|
private NodeService nodeService;
|
|
|
|
private NamespacePrefixResolver namespacePrefixResolver;
|
|
|
|
private AuditModel auditModel;
|
|
|
|
/**
|
|
* Keep hold of the host where the audit occurs. TODO: Check that we get the correct address ...
|
|
*/
|
|
|
|
private InetAddress auditHost;
|
|
|
|
|
|
|
|
/**
|
|
* Default constructor
|
|
*/
|
|
public AuditComponentImpl()
|
|
{
|
|
super();
|
|
// Initialise the host address
|
|
try
|
|
{
|
|
auditHost = InetAddress.getLocalHost();
|
|
}
|
|
catch (UnknownHostException e)
|
|
{
|
|
logger.error("Failed to get local host address", e);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* IOC property setters
|
|
*/
|
|
|
|
/**
|
|
* Set the DAO for recording auditable information when no exception occurs.
|
|
*/
|
|
public void setAuditDAO(AuditDAO auditDAO)
|
|
{
|
|
this.auditDAO = auditDAO;
|
|
}
|
|
|
|
/**
|
|
* Set the DAO for recording failed actions - this is done in another transaction.
|
|
*/
|
|
public void setTransactionService(TransactionService transactionService)
|
|
{
|
|
this.transactionService = transactionService;
|
|
}
|
|
|
|
/**
|
|
* Set the NodeService for path extracting.
|
|
*/
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
/**
|
|
* Set the audit configuration.
|
|
*/
|
|
public void setAuditConfiguration(AuditConfiguration auditConfiguration)
|
|
{
|
|
this.auditConfiguration = auditConfiguration;
|
|
}
|
|
|
|
/**
|
|
* Set the helper used to identify public services
|
|
*/
|
|
public void setPublicServiceIdentifier(PublicServiceIdentifier publicServiceIdentifier)
|
|
{
|
|
this.publicServiceIdentifier = publicServiceIdentifier;
|
|
}
|
|
|
|
/**
|
|
* Set the audit model.
|
|
*/
|
|
public void setAuditModel(AuditModel auditModel)
|
|
{
|
|
this.auditModel = auditModel;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the namespacePrefixResolver.
|
|
*/
|
|
public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver)
|
|
{
|
|
this.namespacePrefixResolver = namespacePrefixResolver;
|
|
}
|
|
|
|
public Object audit(MethodInvocation mi) throws Throwable
|
|
{
|
|
if ((auditFlag.get() == null) || (!auditFlag.get().booleanValue()))
|
|
{
|
|
if (auditModel instanceof AuditEntry && ((AuditEntry) auditModel).getEnabled() == TrueFalseUnset.TRUE)
|
|
{
|
|
boolean auditInternal = (auditModel.getAuditInternalServiceMethods(mi) == TrueFalseUnset.TRUE);
|
|
try
|
|
{
|
|
Method method = mi.getMethod();
|
|
String methodName = method.getName();
|
|
String serviceName = publicServiceIdentifier.getPublicServiceName(mi);
|
|
|
|
if (!auditInternal)
|
|
{
|
|
auditFlag.set(Boolean.TRUE);
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Auditing internal service use for - " + serviceName + "." + methodName);
|
|
}
|
|
}
|
|
|
|
if (method.isAnnotationPresent(Auditable.class))
|
|
{
|
|
|
|
if (serviceName != null)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Auditing - " + serviceName + "." + methodName);
|
|
}
|
|
return auditImpl(mi, true);
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("UnknownService." + methodName);
|
|
}
|
|
return auditImpl(mi, true);
|
|
}
|
|
|
|
}
|
|
else if (method.isAnnotationPresent(NotAuditable.class))
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Not Audited. " + serviceName + "." + methodName);
|
|
}
|
|
return mi.proceed();
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Unannotated service method " + serviceName + "." + methodName);
|
|
}
|
|
if (method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAnnotationPresent(PublicService.class))
|
|
{
|
|
throw new RuntimeException("Unannotated service method " + serviceName + "." + methodName);
|
|
}
|
|
else
|
|
{
|
|
return mi.proceed();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (!auditInternal)
|
|
{
|
|
auditFlag.set(Boolean.FALSE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return mi.proceed();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return mi.proceed();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal audit of a method invocation
|
|
*
|
|
* @param mi -
|
|
* the method to audit
|
|
* @return - the return object from the audited method
|
|
* @throws Throwable -
|
|
* any Throwable that can be thrown by th audtied method.
|
|
*/
|
|
public Object auditImpl(MethodInvocation mi, boolean execute) throws Throwable
|
|
{
|
|
final AuditState auditInfo = new AuditState(auditConfiguration);
|
|
// RecordOptions recordOptions = auditModel.getAuditRecordOptions(mi);
|
|
AuditMode auditMode = AuditMode.UNSET;
|
|
try
|
|
{
|
|
Object o = null;
|
|
auditMode = beforeInvocation(auditMode, auditInfo, mi);
|
|
if (execute)
|
|
{
|
|
o = mi.proceed();
|
|
auditMode = postInvocation(auditMode, auditInfo, mi, o);
|
|
}
|
|
if ((auditMode == AuditMode.ALL) || (auditMode == AuditMode.SUCCESS))
|
|
{
|
|
RetryingTransactionCallback<Object> cb = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
auditDAO.audit(auditInfo);
|
|
return null;
|
|
}
|
|
};
|
|
boolean requiresNew = (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE);
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(cb, false, requiresNew);
|
|
}
|
|
return o;
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
auditMode = onError(auditMode, auditInfo, mi, t);
|
|
if ((auditMode == AuditMode.ALL) || (auditMode == AuditMode.FAIL))
|
|
{
|
|
try
|
|
{
|
|
RetryingTransactionCallback<Object> cb = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
auditDAO.audit(auditInfo);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(cb, false, true);
|
|
}
|
|
catch (Throwable tt)
|
|
{
|
|
throw new AuditException("Failed to audit exception", new Object[] { tt }, t);
|
|
}
|
|
}
|
|
throw t;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to set auditable properties and to determine if auditing is required when an exception is caught in
|
|
* the audited method.
|
|
*
|
|
* @param auditMode
|
|
* @param auditInfo
|
|
* @param mi
|
|
* @param t
|
|
* @return - the audit mode
|
|
*/
|
|
private AuditMode onError(AuditMode auditMode, AuditState auditInfo, MethodInvocation mi, Throwable t)
|
|
{
|
|
if ((auditMode == AuditMode.ALL) || (auditMode == AuditMode.FAIL))
|
|
{
|
|
auditInfo.setFail(true);
|
|
auditInfo.setThrowable(t);
|
|
}
|
|
|
|
return auditMode;
|
|
}
|
|
|
|
/**
|
|
* Helper method to set audited information after method invocation and to determine if auditing should take place
|
|
* based on the method return value.
|
|
*
|
|
* @param auditMode
|
|
* @param auditInfo
|
|
* @param mi
|
|
* @param returnObject
|
|
* @return - the audit mode.
|
|
*/
|
|
private AuditMode postInvocation(AuditMode auditMode, AuditState auditInfo, MethodInvocation mi, Object returnObject)
|
|
{
|
|
if (returnObject == null)
|
|
{
|
|
auditInfo.setReturnObject(null);
|
|
}
|
|
else if (returnObject instanceof Serializable)
|
|
{
|
|
auditInfo.setReturnObject((Serializable) returnObject);
|
|
}
|
|
else
|
|
{
|
|
auditInfo.setReturnObject(returnObject.toString());
|
|
}
|
|
|
|
Auditable auditable = mi.getMethod().getAnnotation(Auditable.class);
|
|
if (auditable.key() == Auditable.Key.RETURN)
|
|
{
|
|
if (returnObject != null)
|
|
{
|
|
if (returnObject instanceof NodeRef)
|
|
{
|
|
NodeRef key = (NodeRef) returnObject;
|
|
auditInfo.setKeyStore(key.getStoreRef());
|
|
auditInfo.setKeyGUID(key.getId());
|
|
RecordOptions recordOptions = auditModel.getAuditRecordOptions(mi);
|
|
if (recordOptions != null && recordOptions.getRecordPath() == TrueFalseUnset.TRUE)
|
|
{
|
|
auditInfo.setPath(getNodePath(key));
|
|
}
|
|
}
|
|
else if (returnObject instanceof StoreRef)
|
|
{
|
|
auditInfo.setKeyStore((StoreRef) returnObject);
|
|
}
|
|
else if (returnObject instanceof ChildAssociationRef)
|
|
{
|
|
ChildAssociationRef car = (ChildAssociationRef) returnObject;
|
|
auditInfo.setKeyStore(car.getChildRef().getStoreRef());
|
|
auditInfo.setKeyGUID(car.getChildRef().getId());
|
|
RecordOptions recordOptions = auditModel.getAuditRecordOptions(mi);
|
|
if (recordOptions != null && recordOptions.getRecordPath() == TrueFalseUnset.TRUE)
|
|
{
|
|
auditInfo.setPath(nodeService.getPath(car.getChildRef()).toString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.warn("Key argument is not a node, store or child assoc ref for return object on "
|
|
+ publicServiceIdentifier.getPublicServiceName(mi) + "." + mi.getMethod().getName() + " it is " + returnObject.getClass().getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the user name is not set, try and set it after the method call.
|
|
// This covers authentication when the user is only known after the call.
|
|
|
|
if (auditInfo.getUserIdentifier() == null)
|
|
{
|
|
auditInfo.setUserIdentifier(AuthenticationUtil.getFullyAuthenticatedUser());
|
|
}
|
|
|
|
return auditMode;
|
|
}
|
|
|
|
/**
|
|
* Set auditable information and determine if auditing is required before method invocation. This would normally be
|
|
* based on the method arguments.
|
|
*
|
|
* @param auditMode
|
|
* @param auditInfo
|
|
* @param mi
|
|
* @return - the audit mode.
|
|
*/
|
|
private AuditMode beforeInvocation(AuditMode auditMode, AuditState auditInfo, MethodInvocation mi)
|
|
{
|
|
AuditMode effectiveAuditMode = auditModel.beforeExecution(auditMode, mi);
|
|
|
|
if (auditMode != AuditMode.NONE)
|
|
{
|
|
String methodName = mi.getMethod().getName();
|
|
String serviceName = publicServiceIdentifier.getPublicServiceName(mi);
|
|
auditInfo.setAuditApplication(SYSTEM_APPLICATION);
|
|
auditInfo.setAuditConfiguration(auditConfiguration);
|
|
auditInfo.setAuditMethod(methodName);
|
|
auditInfo.setAuditService(serviceName);
|
|
auditInfo.setClientAddress(null);
|
|
auditInfo.setDate(new Date());
|
|
auditInfo.setFail(false);
|
|
auditInfo.setFiltered(false);
|
|
auditInfo.setHostAddress(auditHost);
|
|
|
|
auditInfo.setPath(null);
|
|
|
|
Auditable auditable = mi.getMethod().getAnnotation(Auditable.class);
|
|
Object key = null;
|
|
switch (auditable.key())
|
|
{
|
|
case ARG_0:
|
|
checkArgLength(mi, methodName, serviceName, 0);
|
|
key = mi.getArguments()[0];
|
|
break;
|
|
case ARG_1:
|
|
checkArgLength(mi, methodName, serviceName, 1);
|
|
key = mi.getArguments()[1];
|
|
break;
|
|
case ARG_2:
|
|
checkArgLength(mi, methodName, serviceName, 2);
|
|
key = mi.getArguments()[2];
|
|
break;
|
|
case ARG_3:
|
|
checkArgLength(mi, methodName, serviceName, 3);
|
|
key = mi.getArguments()[3];
|
|
break;
|
|
case ARG_4:
|
|
checkArgLength(mi, methodName, serviceName, 4);
|
|
key = mi.getArguments()[4];
|
|
break;
|
|
case ARG_5:
|
|
checkArgLength(mi, methodName, serviceName, 5);
|
|
key = mi.getArguments()[5];
|
|
break;
|
|
case ARG_6:
|
|
checkArgLength(mi, methodName, serviceName, 6);
|
|
key = mi.getArguments()[6];
|
|
break;
|
|
case ARG_7:
|
|
checkArgLength(mi, methodName, serviceName, 7);
|
|
key = mi.getArguments()[7];
|
|
break;
|
|
case ARG_8:
|
|
checkArgLength(mi, methodName, serviceName, 8);
|
|
key = mi.getArguments()[8];
|
|
break;
|
|
case ARG_9:
|
|
checkArgLength(mi, methodName, serviceName, 9);
|
|
key = mi.getArguments()[9];
|
|
break;
|
|
case NO_KEY:
|
|
default:
|
|
break;
|
|
}
|
|
if (key != null)
|
|
{
|
|
RecordOptions recordOptions = auditModel.getAuditRecordOptions(mi);
|
|
if (key instanceof NodeRef)
|
|
{
|
|
auditInfo.setKeyStore(((NodeRef) key).getStoreRef());
|
|
auditInfo.setKeyGUID(((NodeRef) key).getId());
|
|
if (recordOptions != null && recordOptions.getRecordPath() == TrueFalseUnset.TRUE)
|
|
{
|
|
auditInfo.setPath(getNodePath((NodeRef) key));
|
|
}
|
|
}
|
|
else if (key instanceof StoreRef)
|
|
{
|
|
auditInfo.setKeyStore((StoreRef) key);
|
|
}
|
|
else if (key instanceof ChildAssociationRef)
|
|
{
|
|
ChildAssociationRef car = (ChildAssociationRef) key;
|
|
auditInfo.setKeyStore(car.getParentRef().getStoreRef());
|
|
auditInfo.setKeyGUID(car.getParentRef().getId());
|
|
if (recordOptions != null && recordOptions.getRecordPath() == TrueFalseUnset.TRUE)
|
|
{
|
|
auditInfo.setPath(getNodePath(car.getParentRef()));
|
|
}
|
|
}
|
|
else if (key instanceof SearchParameters)
|
|
{
|
|
SearchParameters sp = (SearchParameters) key;
|
|
if (sp.getStores().size() > 0)
|
|
{
|
|
auditInfo.setKeyStore(sp.getStores().get(0));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.warn("Key argument is not a node, store or child assoc reference or search parameters on "
|
|
+ serviceName + "." + methodName + " it is " + key.getClass().getName());
|
|
}
|
|
}
|
|
auditInfo.setKeyPropertiesAfter(null);
|
|
auditInfo.setKeyPropertiesBefore(null);
|
|
auditInfo.setMessage(null);
|
|
if (mi.getArguments() != null)
|
|
{
|
|
Serializable[] serArgs = new Serializable[mi.getArguments().length];
|
|
for (int i = 0; i < mi.getArguments().length; i++)
|
|
{
|
|
if ((auditable.recordable() == null) || (auditable.recordable().length <= i) || auditable.recordable()[i])
|
|
{
|
|
if (mi.getArguments()[i] == null)
|
|
{
|
|
serArgs[i] = null;
|
|
}
|
|
else if (mi.getArguments()[i] instanceof Serializable)
|
|
{
|
|
serArgs[i] = (Serializable) mi.getArguments()[i];
|
|
}
|
|
else
|
|
{
|
|
serArgs[i] = mi.getArguments()[i].toString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
serArgs[i] = "********";
|
|
}
|
|
}
|
|
auditInfo.setMethodArguments(serArgs);
|
|
}
|
|
auditInfo.setReturnObject(null);
|
|
auditInfo.setSessionId(null);
|
|
auditInfo.setThrowable(null);
|
|
auditInfo.setTxId(AlfrescoTransactionSupport.getTransactionId());
|
|
auditInfo.setUserIdentifier(AuthenticationUtil.getFullyAuthenticatedUser());
|
|
}
|
|
|
|
return effectiveAuditMode;
|
|
}
|
|
|
|
private void checkArgLength(MethodInvocation mi, String methodName, String serviceName, int position)
|
|
{
|
|
if (mi.getArguments().length <= position)
|
|
{
|
|
logger.warn("Auditable annotation on " + serviceName + "." + methodName + " references non existant argument");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see org.alfresco.repo.audit.AuditComponent#beforeMethodCallManualAudit(org.aopalliance.intercept.MethodInvocation)
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public void beforeMethodCallManualAudit(Class clazz, Object target, String methodName, Object ... args)
|
|
{
|
|
Class[] argTypes = new Class[args.length];
|
|
for(int i = 0; i < args.length; i++)
|
|
{
|
|
argTypes[i] = args[i].getClass();
|
|
}
|
|
Method method;
|
|
try
|
|
{
|
|
method = clazz.getMethod(methodName, argTypes);
|
|
}
|
|
catch (SecurityException e1)
|
|
{
|
|
return;
|
|
}
|
|
catch (NoSuchMethodException e1)
|
|
{
|
|
return;
|
|
}
|
|
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(null, target, method, args, null, null) {};
|
|
if ((auditFlag.get() == null) || (!auditFlag.get().booleanValue()))
|
|
{
|
|
if (auditModel instanceof AuditEntry && ((AuditEntry) auditModel).getEnabled() == TrueFalseUnset.TRUE)
|
|
{
|
|
boolean auditInternal = (auditModel.getAuditInternalServiceMethods(methodInvocation) == TrueFalseUnset.TRUE);
|
|
try
|
|
{
|
|
String serviceName = publicServiceIdentifier.getPublicServiceName(methodInvocation);
|
|
|
|
if (!auditInternal)
|
|
{
|
|
auditFlag.set(Boolean.TRUE);
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Auditing internal service use for - " + serviceName + "." + methodName);
|
|
}
|
|
}
|
|
|
|
if (method.isAnnotationPresent(Auditable.class))
|
|
{
|
|
|
|
if (serviceName != null)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Auditing - " + serviceName + "." + methodName);
|
|
}
|
|
try
|
|
{
|
|
auditImpl(methodInvocation, false);
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("UnknownService." + methodName);
|
|
}
|
|
try
|
|
{
|
|
auditImpl(methodInvocation, false);
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (method.isAnnotationPresent(NotAuditable.class))
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Not Audited. " + serviceName + "." + methodName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Unannotated service method " + serviceName + "." + methodName);
|
|
}
|
|
if (method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAnnotationPresent(PublicService.class))
|
|
{
|
|
throw new RuntimeException("Unannotated service method " + serviceName + "." + methodName);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (!auditInternal)
|
|
{
|
|
auditFlag.set(Boolean.FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A simple audit entry Currently we ignore filtering here.
|
|
*/
|
|
public void audit(String source, String description, NodeRef key, Object... args)
|
|
{
|
|
final AuditState auditInfo = new AuditState(auditConfiguration);
|
|
AuditMode auditMode = AuditMode.UNSET;
|
|
try
|
|
{
|
|
auditMode = onApplicationAudit(auditMode, auditInfo, source, description, key, args);
|
|
if ((auditMode == AuditMode.ALL) || (auditMode == AuditMode.SUCCESS))
|
|
{
|
|
RetryingTransactionCallback<Object> cb = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
auditDAO.audit(auditInfo);
|
|
return null;
|
|
}
|
|
};
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(cb, false, false);
|
|
}
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
auditMode = onError(auditMode, auditInfo, t, source, description, key, args);
|
|
if ((auditMode == AuditMode.ALL) || (auditMode == AuditMode.FAIL))
|
|
{
|
|
try
|
|
{
|
|
RetryingTransactionCallback<Object> cb = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
auditDAO.audit(auditInfo);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(cb, false, true);
|
|
}
|
|
catch (Throwable tt)
|
|
{
|
|
throw new AuditException("Failed to audit exception", new Object[] { tt }, t);
|
|
}
|
|
}
|
|
throw new AuditException("Application audit failed", t);
|
|
}
|
|
}
|
|
|
|
public List<AuditInfo> getAuditTrail(NodeRef nodeRef)
|
|
{
|
|
return auditDAO.getAuditTrail(nodeRef);
|
|
}
|
|
|
|
private AuditMode onApplicationAudit(AuditMode auditMode, AuditState auditInfo, String source, String description, NodeRef key, Object... args)
|
|
{
|
|
AuditMode effectiveAuditMode = auditModel.beforeExecution(auditMode, source, description, key, args);
|
|
auditModel.getAuditRecordOptions(source);
|
|
if (auditMode != AuditMode.NONE)
|
|
{
|
|
if (source.equals(SYSTEM_APPLICATION))
|
|
{
|
|
throw new AuditException("Application audit can not use the reserved identifier " + SYSTEM_APPLICATION);
|
|
}
|
|
|
|
auditInfo.setAuditApplication(source);
|
|
auditInfo.setAuditConfiguration(auditConfiguration);
|
|
auditInfo.setAuditMethod(null);
|
|
auditInfo.setAuditService(null);
|
|
auditInfo.setClientAddress(null);
|
|
auditInfo.setDate(new Date());
|
|
auditInfo.setFail(false);
|
|
auditInfo.setFiltered(false);
|
|
auditInfo.setHostAddress(auditHost);
|
|
auditInfo.setPath(null);
|
|
if (key != null)
|
|
{
|
|
auditInfo.setKeyStore(key.getStoreRef());
|
|
auditInfo.setKeyGUID(key.getId());
|
|
RecordOptions recordOptions = auditModel.getAuditRecordOptions(source);
|
|
if (recordOptions != null && recordOptions.getRecordPath() == TrueFalseUnset.TRUE)
|
|
{
|
|
auditInfo.setPath(getNodePath(key));
|
|
}
|
|
}
|
|
auditInfo.setKeyPropertiesAfter(null);
|
|
auditInfo.setKeyPropertiesBefore(null);
|
|
auditInfo.setMessage(description);
|
|
if (args != null)
|
|
{
|
|
Serializable[] serArgs = new Serializable[args.length];
|
|
for (int i = 0; i < args.length; i++)
|
|
{
|
|
if (args[i] == null)
|
|
{
|
|
serArgs[i] = null;
|
|
}
|
|
else if (args[i] instanceof Serializable)
|
|
{
|
|
serArgs[i] = (Serializable) args[i];
|
|
}
|
|
else
|
|
{
|
|
serArgs[i] = args[i].toString();
|
|
}
|
|
}
|
|
auditInfo.setMethodArguments(serArgs);
|
|
}
|
|
auditInfo.setReturnObject(null);
|
|
auditInfo.setSessionId(null);
|
|
auditInfo.setThrowable(null);
|
|
auditInfo.setTxId(AlfrescoTransactionSupport.getTransactionId());
|
|
auditInfo.setUserIdentifier(AuthenticationUtil.getFullyAuthenticatedUser());
|
|
}
|
|
|
|
return effectiveAuditMode;
|
|
}
|
|
|
|
private AuditMode onError(AuditMode auditMode, AuditState auditInfo, Throwable t, String source, String description, NodeRef key, Object... args)
|
|
{
|
|
if ((auditMode == AuditMode.ALL) || (auditMode == AuditMode.FAIL))
|
|
{
|
|
auditInfo.setFail(true);
|
|
auditInfo.setThrowable(t);
|
|
}
|
|
|
|
return auditMode;
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns human readable path for node.
|
|
* To improve performance may return simple toString() method of the path.
|
|
*
|
|
* @param nodeRef
|
|
* @return Human readable path for node
|
|
*/
|
|
private String getNodePath(NodeRef nodeRef)
|
|
{
|
|
String result = null;
|
|
if (nodeService.exists(nodeRef))
|
|
{
|
|
Path path = nodeService.getPath(nodeRef);
|
|
return path.toPrefixString(namespacePrefixResolver);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* V3.2 from here on. Put all fixes to the older audit code before this point, please.
|
|
*/
|
|
|
|
private AuditModelRegistry auditModelRegistry;
|
|
private PropertyValueDAO propertyValueDAO;
|
|
|
|
/**
|
|
* Set the registry holding the audit models
|
|
* @since 3.2
|
|
*/
|
|
public void setAuditModelRegistry(AuditModelRegistry auditModelRegistry)
|
|
{
|
|
this.auditModelRegistry = auditModelRegistry;
|
|
}
|
|
|
|
/**
|
|
* Set the DAO for manipulating property values
|
|
* @since 3.2
|
|
*/
|
|
public void setPropertyValueDAO(PropertyValueDAO propertyValueDAO)
|
|
{
|
|
this.propertyValueDAO = propertyValueDAO;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 3.2
|
|
*/
|
|
public void 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;
|
|
}
|
|
|
|
Long applicationId = application.getApplicationId();
|
|
|
|
auditDAO.deleteAuditEntries(applicationId, fromTime, toTime);
|
|
// Done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Delete audit entries for " + applicationName + " (" + fromTime + " to " + toTime);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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.2
|
|
*/
|
|
public boolean isSourcePathMapped(String sourcePath)
|
|
{
|
|
return isAuditEnabled() && !auditModelRegistry.getAuditPathMapper().isEmpty();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 3.2
|
|
*/
|
|
public boolean isAuditPathEnabled(String applicationName, String path)
|
|
{
|
|
ParameterCheck.mandatory("applicationName", applicationName);
|
|
ParameterCheck.mandatory("path", path);
|
|
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;
|
|
}
|
|
// Check the path against the application
|
|
application.checkPath(path);
|
|
|
|
Set<String> disabledPaths = getDisabledPaths(application);
|
|
|
|
// Check if there are any entries that match or superced 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);
|
|
ParameterCheck.mandatory("path", path);
|
|
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;
|
|
}
|
|
// 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);
|
|
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;
|
|
}
|
|
// 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() || !isSourcePathMapped(rootPath))
|
|
{
|
|
return Collections.emptyMap();
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Eliminate any paths that have been disabled
|
|
Iterator<String> pathedValuesKeyIterator = values.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 (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();
|
|
}
|
|
|
|
// Generate data
|
|
Map<String, DataGenerator> generators = application.getDataGenerators(values.keySet());
|
|
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())
|
|
{
|
|
logger.debug(
|
|
"New audit entry: \n" +
|
|
" Application ID: " + applicationId + "\n" +
|
|
" Entry ID: " + entryId + "\n" +
|
|
" Values: " + values + "\n" +
|
|
" Audit Data: " + auditData);
|
|
}
|
|
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() + 5);
|
|
for (Map.Entry<String, Serializable> entry : values.entrySet())
|
|
{
|
|
String path = entry.getKey();
|
|
Serializable value = entry.getValue();
|
|
// Get the applicable extractor
|
|
Map<String, DataExtractor> extractors = application.getDataExtractors(path);
|
|
for (Map.Entry<String, DataExtractor> extractorElement : extractors.entrySet())
|
|
{
|
|
String extractorPath = extractorElement.getKey();
|
|
DataExtractor extractor = extractorElement.getValue();
|
|
// 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: " + path + "\n" +
|
|
" Raw value: " + value + "\n" +
|
|
" Extractor: " + extractor,
|
|
e);
|
|
}
|
|
// Add it to the map
|
|
newData.put(extractorPath, data);
|
|
}
|
|
}
|
|
// Done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Extracted audit data: \n" +
|
|
" Application: " + application + "\n" +
|
|
" Raw values: " + values + "\n" +
|
|
" Extracted: " + newData);
|
|
}
|
|
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);
|
|
}
|
|
}
|