/*
* Copyright (C) 2005-2007 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 recieved 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.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.audit.extractor.DataExtractor;
import org.alfresco.repo.audit.generator.DataGenerator;
import org.alfresco.repo.audit.generator.DataGenerator.DataGeneratorScope;
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.security.authentication.AuthenticationUtil;
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.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.ParameterCheck;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 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.
*
* 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 auditFlag = new ThreadLocal();
/**
* 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);
}
else
{
if (logger.isDebugEnabled())
{
logger.debug("UnknownService." + methodName);
}
return auditImpl(mi);
}
}
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) throws Throwable
{
final AuditState auditInfo = new AuditState(auditConfiguration);
// RecordOptions recordOptions = auditModel.getAuditRecordOptions(mi);
AuditMode auditMode = AuditMode.UNSET;
try
{
auditMode = beforeInvocation(auditMode, auditInfo, mi);
Object o = mi.proceed();
auditMode = postInvocation(auditMode, auditInfo, mi, o);
if ((auditMode == AuditMode.ALL) || (auditMode == AuditMode.SUCCESS))
{
RetryingTransactionCallback