Audit validation using XSD and related tests

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15875 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-08-24 20:10:49 +00:00
parent b8c93f89f3
commit 598ecfb27b
27 changed files with 1112 additions and 224 deletions

View File

@@ -79,6 +79,7 @@
<!-- --> <!-- -->
<bean id="auditModel.registry" class="org.alfresco.repo.audit.model.AuditModelRegistry"> <bean id="auditModel.registry" class="org.alfresco.repo.audit.model.AuditModelRegistry">
<property name="transactionService" ref="transactionService"/>
<property name="auditDAO" ref="auditDAO"/> <property name="auditDAO" ref="auditDAO"/>
</bean> </bean>

View File

@@ -57,7 +57,6 @@
<xs:complexContent> <xs:complexContent>
<xs:extension base="a:KeyedAuditDefinition"> <xs:extension base="a:KeyedAuditDefinition">
<xs:sequence> <xs:sequence>
<xs:element name="AuditSession" type="a:AuditSession" minOccurs="0" maxOccurs="1"/>
<xs:element name="RecordValue" type="a:RecordValue" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="RecordValue" type="a:RecordValue" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="GenerateValue" type="a:GenerateValue" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="GenerateValue" type="a:GenerateValue" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="AuditPath" type="a:AuditPath" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="AuditPath" type="a:AuditPath" minOccurs="0" maxOccurs="unbounded"/>
@@ -66,12 +65,6 @@
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
<xs:complexType name="AuditSession">
<xs:sequence>
<xs:element name="GenerateValue" type="a:GenerateValue" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="RecordValue"> <xs:complexType name="RecordValue">
<xs:complexContent> <xs:complexContent>
<xs:extension base="a:KeyedAuditDefinition"> <xs:extension base="a:KeyedAuditDefinition">
@@ -84,6 +77,7 @@
<xs:complexContent> <xs:complexContent>
<xs:extension base="a:KeyedAuditDefinition"> <xs:extension base="a:KeyedAuditDefinition">
<xs:attribute name="dataGenerator" type="a:NameAttribute" use="required" /> <xs:attribute name="dataGenerator" type="a:NameAttribute" use="required" />
<xs:attribute name="scope" type="a:ScopeAttribute" use="required" />
</xs:extension> </xs:extension>
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
@@ -108,7 +102,7 @@
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:minLength value="1"/> <xs:minLength value="1"/>
<xs:maxLength value="128"/> <xs:maxLength value="128"/>
<xs:pattern value="([a-z]|\-)*"/> <xs:pattern value="([a-z]|[0-9]|\-|\.)*"/>
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
@@ -118,4 +112,12 @@
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
<xs:simpleType name="ScopeAttribute">
<xs:restriction base="xs:string">
<xs:enumeration value="SESSION"/>
<xs:enumeration value="AUDIT"/>
<xs:enumeration value="ALL"/>
</xs:restriction>
</xs:simpleType>
</xs:schema> </xs:schema>

View File

@@ -23,12 +23,10 @@
</DataGenerators> </DataGenerators>
<Application name="Alfresco Repository" key="repository"> <Application name="Alfresco Repository" key="repository">
<AuditSession> <GenerateValue key="user" dataGenerator="authenticatedUser" scope="SESSION"/>
<GenerateValue key="user" dataGenerator="authenticatedUser"/> <GenerateValue key="time" dataGenerator="systemTime" scope="SESSION"/>
<GenerateValue key="time" dataGenerator="systemTime"/>
</AuditSession>
<AuditPath key="services"> <AuditPath key="services">
<GenerateValue key="txn" dataGenerator="transactionId"/> <GenerateValue key="txn" dataGenerator="transactionId" scope="AUDIT"/>
<AuditPath key="nodeservice"> <AuditPath key="nodeservice">
<AuditPath key="createstore"> <AuditPath key="createstore">
<AuditPath key="protocol"> <AuditPath key="protocol">
@@ -39,10 +37,12 @@
</AuditPath> </AuditPath>
<AuditPath key="return"> <AuditPath key="return">
<RecordValue key="value" dataExtractor="simpleValue"/> <RecordValue key="value" dataExtractor="simpleValue"/>
<!--
<RecordValue key="root-node" dataExtractor="storeRootNode"/> <RecordValue key="root-node" dataExtractor="storeRootNode"/>
</AuditPath> </AuditPath>
<AuditPath key="error"> <AuditPath key="error">
<RecordValue key="value" dataExtractor="stackTrace"/> <RecordValue key="value" dataExtractor="stackTrace"/>
-->
</AuditPath> </AuditPath>
</AuditPath> </AuditPath>
</AuditPath> </AuditPath>

View File

@@ -24,11 +24,20 @@
*/ */
package org.alfresco.repo.audit; package org.alfresco.repo.audit;
import java.net.URL;
import java.util.Map;
import junit.framework.TestCase; import junit.framework.TestCase;
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.AuditModelException;
import org.alfresco.repo.audit.model.AuditModelRegistry; import org.alfresco.repo.audit.model.AuditModelRegistry;
import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.util.ResourceUtils;
/** /**
* Tests that auditing is loaded properly on repository startup. * Tests that auditing is loaded properly on repository startup.
@@ -40,7 +49,7 @@ import org.springframework.context.ApplicationContext;
*/ */
public class AuditBootstrapTest extends TestCase public class AuditBootstrapTest extends TestCase
{ {
private static final String APPLICATION_REPOSITORY = "Alfresco Repository"; private static final String APPLICATION_TEST = "Alfresco Test";
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
@@ -50,6 +59,11 @@ public class AuditBootstrapTest extends TestCase
public void setUp() throws Exception public void setUp() throws Exception
{ {
auditModelRegistry = (AuditModelRegistry) ctx.getBean("auditModel.registry"); auditModelRegistry = (AuditModelRegistry) ctx.getBean("auditModel.registry");
// Register a new model
URL testModelUrl = ResourceUtils.getURL("classpath:alfresco/audit/alfresco-audit-test.xml");
auditModelRegistry.registerModel(testModelUrl);
auditModelRegistry.loadAuditModels();
} }
public void testSetUp() public void testSetUp()
@@ -57,9 +71,116 @@ public class AuditBootstrapTest extends TestCase
// Just here to fail if the basic startup fails // Just here to fail if the basic startup fails
} }
private void loadBadModel(String url) throws Exception
{
try
{
URL testModelUrl = ResourceUtils.getURL(url);
auditModelRegistry.registerModel(testModelUrl);
auditModelRegistry.loadAuditModels();
fail("Expected model loading to fail.");
}
catch (AuditModelException e)
{
// Expected
}
}
public void testModelLoading_NoDataExtractor() throws Exception
{
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-01.xml");
}
public void testModelLoading_NoDataGenerator() throws Exception
{
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-02.xml");
}
public void testModelLoading_DuplicatePath() throws Exception
{
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-03.xml");
}
public void testModelLoading_UppercasePath() throws Exception
{
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-04.xml");
}
public void testModelLoading_InvalidScope() throws Exception
{
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-05.xml");
}
public void testGetModelId() public void testGetModelId()
{ {
Long repoId = auditModelRegistry.getAuditModelId(APPLICATION_REPOSITORY); Long repoId = auditModelRegistry.getAuditModelId(APPLICATION_TEST);
assertNotNull("No audit model ID for " + APPLICATION_REPOSITORY, repoId); assertNotNull("No audit model ID for " + APPLICATION_TEST, repoId);
}
private void testBadPath(AuditApplication app, String path)
{
try
{
app.checkPath(path);
fail("Expected path check to fail.");
}
catch (AuditModelException e)
{
// Expected
}
}
public void testAuditApplication_Path()
{
AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST);
assertNotNull(app);
// Check that path checks are working
testBadPath(app, null);
testBadPath(app, "");
testBadPath(app, "test");
testBadPath(app, "/Test");
testBadPath(app, "/test/");
}
public void testAuditApplication_GetDataExtractors()
{
AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST);
assertNotNull(app);
Map<String, DataExtractor> extractors = app.getDataExtractors("/blah");
assertNotNull("Should never get a null map", extractors);
assertTrue("Expected no extractors", extractors.isEmpty());
extractors = app.getDataExtractors("/test/1.1/2.1/3.1/4.1");
assertEquals(2, extractors.size());
assertTrue(extractors.containsKey("/test/1.1/2.1/3.1/value.1"));
assertTrue(extractors.containsKey("/test/1.1/2.1/3.1/4.1/value.1"));
}
public void testAuditApplication_GetDataGenerators_AnyScope()
{
AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST);
assertNotNull(app);
Map<String, DataGenerator> generators = app.getDataGenerators("/blah", DataGeneratorScope.ALL);
assertNotNull("Should never get a null map", generators);
assertTrue("Expected no generators", generators.isEmpty());
generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1", DataGeneratorScope.ALL);
assertEquals(1, generators.size());
assertTrue(generators.containsKey("/test/time"));
generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1", DataGeneratorScope.SESSION);
assertEquals(1, generators.size());
assertTrue(generators.containsKey("/test/time"));
generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1", DataGeneratorScope.AUDIT);
assertEquals(0, generators.size());
generators = app.getDataGenerators("/test/1.1/2.2/3.2/4.1", DataGeneratorScope.ALL);
assertEquals(2, generators.size());
assertTrue(generators.containsKey("/test/time"));
assertTrue(generators.containsKey("/test/1.1/2.2/3.2/4.1/time"));
} }
} }

View File

@@ -91,14 +91,14 @@ public interface AuditComponent
* *
* @param application the name of the application to log against * @param application the name of the application to log against
* @param rootPath a base path of {@link AuditPath} key entries concatenated with <b>.</b> (period) * @param rootPath a base path of {@link AuditPath} key entries concatenated with <b>.</b> (period)
* @return Returns the unique session identifier * @return Returns the unique session
*/ */
public Long startAuditSession(String application, String rootPath); public AuditSession startAuditSession(String application, String rootPath);
/** /**
* Record a set of values against the given session. * Record a set of values against the given session.
* *
* @param sessionId a pre-existing audit session to continue with * @param session a pre-existing audit session to continue with
* @param values the values to audit mapped by {@link AuditPath} key relative to the session * @param values the values to audit mapped by {@link AuditPath} key relative to the session
* root path * root path
* *
@@ -106,5 +106,5 @@ public interface AuditComponent
* *
* @since 3.2 * @since 3.2
*/ */
public void audit(Long sessionId, Map<String, Object> values); public void audit(AuditSession session, Map<String, Object> values);
} }

View File

@@ -32,14 +32,13 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.alfresco.repo.audit.model.AuditApplication;
import org.alfresco.repo.audit.model.AuditEntry; import org.alfresco.repo.audit.model.AuditEntry;
import org.alfresco.repo.audit.model.AuditModelRegistry; import org.alfresco.repo.audit.model.AuditModelRegistry;
import org.alfresco.repo.audit.model.TrueFalseUnset; import org.alfresco.repo.audit.model.TrueFalseUnset;
import org.alfresco.repo.audit.model._3.Application;
import org.alfresco.repo.domain.audit.AuditDAO; import org.alfresco.repo.domain.audit.AuditDAO;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.Auditable; import org.alfresco.service.Auditable;
@@ -752,8 +751,7 @@ public class AuditComponentImpl implements AuditComponent
* V3.2 from here on. Put all fixes to the older audit code before this point, please. * V3.2 from here on. Put all fixes to the older audit code before this point, please.
*/ */
private static final Long NO_AUDIT_SESSION = new Long(-1); private static final AuditSession NO_AUDIT_SESSION = new AuditSession();
private static final String KEY_AUDIT_SESSION = "Audit.Sessions";
private AuditModelRegistry auditModelRegistry; private AuditModelRegistry auditModelRegistry;
@@ -766,13 +764,10 @@ public class AuditComponentImpl implements AuditComponent
this.auditModelRegistry = auditModelRegistry; this.auditModelRegistry = auditModelRegistry;
} }
public Long startAuditSession(String applicationName, String rootPath) public AuditSession startAuditSession(String applicationName, String rootPath)
{ {
// First check that we can store a session on the transaction
Map<Long, AuditSession> sessionsById = TransactionalResourceHelper.getMap(KEY_AUDIT_SESSION);
// Get the application // Get the application
Application application = auditModelRegistry.getAuditApplication(applicationName); AuditApplication application = auditModelRegistry.getAuditApplication(applicationName);
if (application == null) if (application == null)
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
@@ -788,25 +783,34 @@ public class AuditComponentImpl implements AuditComponent
throw new AuditException("No model exists for audit application: " + applicationName); throw new AuditException("No model exists for audit application: " + applicationName);
} }
// TODO: Validate root path against application // Validate root path against application
String appRootKey = application.getApplicationKey() + AuditApplication.AUDIT_PATH_SEPARATOR;
if (rootPath == null || !rootPath.startsWith(appRootKey))
{
throw new AuditException(
"An audit session's root path must start with the application's root key.\n" +
" Application: " + application.getApplicationName() + "\n" +
" Root key: " + application.getApplicationKey() + "\n" +
" Given root: " + rootPath);
}
// TODO: Pull out session properties and persist // TODO: Pull out session properties and persist
// Now create the session // Now create the session
Long sessionId = auditDAO.createAuditSession(modelId, applicationName); Long sessionId = auditDAO.createAuditSession(modelId, applicationName);
// Create the session info and store it on the transaction // Create the session info and store it on the transaction
AuditSession session = new AuditSession(application, rootPath, sessionId); AuditSession session = new AuditSession(application, rootPath, sessionId);
sessionsById.put(sessionId, session);
// Done // Done
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug("New audit session: " + session); logger.debug("New audit session: " + session);
} }
return sessionId; return session;
} }
public void audit(Long sessionId, Map<String, Object> values) public void audit(AuditSession session, Map<String, Object> values)
{ {
if (sessionId == NO_AUDIT_SESSION) if (session == NO_AUDIT_SESSION)
{ {
// For some reason, the session was not to be used // For some reason, the session was not to be used
return; return;

View File

@@ -24,7 +24,7 @@
*/ */
package org.alfresco.repo.audit; package org.alfresco.repo.audit;
import org.alfresco.repo.audit.model._3.Application; import org.alfresco.repo.audit.model.AuditApplication;
import org.alfresco.util.ParameterCheck; import org.alfresco.util.ParameterCheck;
/** /**
@@ -35,16 +35,26 @@ import org.alfresco.util.ParameterCheck;
*/ */
public class AuditSession public class AuditSession
{ {
private final Application application; private final AuditApplication application;
private final String rootPath; private final String rootPath;
private final Long sessionId; private final Long sessionId;
/**
* Constructor used to denote a dummy (no-audit) session
*/
/* package */ AuditSession()
{
application = null;
rootPath = null;
sessionId = null;
}
/** /**
* @param application the audit application config being used * @param application the audit application config being used
* @param rootPath the root path being used for the session * @param rootPath the root path being used for the session
* @param sessionId the ID produced for the persisted session * @param sessionId the ID produced for the persisted session
*/ */
public AuditSession(Application application, String rootPath, Long sessionId) public AuditSession(AuditApplication application, String rootPath, Long sessionId)
{ {
ParameterCheck.mandatory("application", application); ParameterCheck.mandatory("application", application);
ParameterCheck.mandatoryString("rootPath", rootPath); ParameterCheck.mandatoryString("rootPath", rootPath);
@@ -58,7 +68,7 @@ public class AuditSession
@Override @Override
public int hashCode() public int hashCode()
{ {
return (application.getName().hashCode() + rootPath.hashCode()); return (application.hashCode() + rootPath.hashCode());
} }
@Override @Override
@@ -71,7 +81,7 @@ public class AuditSession
else if (obj instanceof AuditSession) else if (obj instanceof AuditSession)
{ {
AuditSession that = (AuditSession) obj; AuditSession that = (AuditSession) obj;
return this.application.getName().equals(that.application.getName()) && return this.application.equals(that.application) &&
this.rootPath.equals(that.rootPath); this.rootPath.equals(that.rootPath);
} }
else else
@@ -85,14 +95,14 @@ public class AuditSession
{ {
StringBuilder sb = new StringBuilder(512); StringBuilder sb = new StringBuilder(512);
sb.append("AuditSession") sb.append("AuditSession")
.append("[ application=").append(application.getName()) .append("[ application=").append(application)
.append(", rootPath=").append(rootPath) .append(", rootPath=").append(rootPath)
.append(", sessionId=").append(sessionId) .append(", sessionId=").append(sessionId)
.append("]"); .append("]");
return sb.toString(); return sb.toString();
} }
public Application getApplication() public AuditApplication getApplication()
{ {
return application; return application;
} }

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2005-2009 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.extractor;
/**
* Abstract implementation to provide support.
*
* @author Derek Hulley
* @since 3.2
*/
public abstract class AbstractDataExtractor implements DataExtractor
{
/**
* This implementation assumes all extractors are stateless i.e. if the class matches
* then the instances are equal.
*/
@Override
public boolean equals(Object obj)
{
if (obj != null && obj.getClass().equals(this.getClass()))
{
return true;
}
else
{
return false;
}
}
}

View File

@@ -32,7 +32,7 @@ package org.alfresco.repo.audit.extractor;
* @author Derek Hulley * @author Derek Hulley
* @since 3.2 * @since 3.2
*/ */
public class SimpleValueDataExtractor implements DataExtractor public class SimpleValueDataExtractor extends AbstractDataExtractor
{ {
/** /**
* @return Returns <tt>true</tt> always * @return Returns <tt>true</tt> always

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2005-2009 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.generator;
/**
* Abstract implementation to provide support.
*
* @author Derek Hulley
* @since 3.2
*/
public abstract class AbstractDataGenerator implements DataGenerator
{
/**
* This implementation assumes all generators are stateless i.e. if the class matches
* then the instances are equal.
*/
@Override
public boolean equals(Object obj)
{
if (obj != null && obj.getClass().equals(this.getClass()))
{
return true;
}
else
{
return false;
}
}
}

View File

@@ -32,7 +32,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil;
* @author Derek Hulley * @author Derek Hulley
* @since 3.2 * @since 3.2
*/ */
public class AuthenticatedUserDataGenerator implements DataGenerator public class AuthenticatedUserDataGenerator extends AbstractDataGenerator
{ {
/** /**
* @return Returns the currently-authenticated user * @return Returns the currently-authenticated user

View File

@@ -36,6 +36,16 @@ package org.alfresco.repo.audit.generator;
*/ */
public interface DataGenerator public interface DataGenerator
{ {
public static enum DataGeneratorScope
{
/** Data is generated for new audit sessions only */
SESSION,
/** Data is generated for individual audit calls only */
AUDIT,
/** Data is generated for new audit sessions and audit calls */
ALL
}
/** /**
* Get the data generated by the instance. * Get the data generated by the instance.
* *

View File

@@ -32,7 +32,7 @@ import java.util.Date;
* @author Derek Hulley * @author Derek Hulley
* @since 3.2 * @since 3.2
*/ */
public class SystemTimeDataGenerator implements DataGenerator public class SystemTimeDataGenerator extends AbstractDataGenerator
{ {
/** /**
* @return Returns the current time * @return Returns the current time

View File

@@ -32,7 +32,7 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
* @author Derek Hulley * @author Derek Hulley
* @since 3.2 * @since 3.2
*/ */
public class TransactionIdDataGenerator implements DataGenerator public class TransactionIdDataGenerator extends AbstractDataGenerator
{ {
/** /**
* @return Returns the current transaction ID (<tt>null</tt> if not in a transction) * @return Returns the current transaction ID (<tt>null</tt> if not in a transction)

View File

@@ -0,0 +1,395 @@
/*
* Copyright (C) 2005-2009 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.model;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
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._3.Application;
import org.alfresco.repo.audit.model._3.AuditPath;
import org.alfresco.repo.audit.model._3.GenerateValue;
import org.alfresco.repo.audit.model._3.RecordValue;
import org.alfresco.repo.audit.model._3.ScopeAttribute;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Helper class that wraps the {@link Application audit application}.
* Once wrapped, client code doesn't need access to any of the generated
* model-driven classes.
*
* @author Derek Hulley
* @since 3.2
*/
public class AuditApplication
{
public static final String AUDIT_PATH_SEPARATOR = "/";
private static final Log logger = LogFactory.getLog(AuditApplication.class);
private final String applicationName;
private final String applicationKey;
private final Map<String, DataExtractor> dataExtractorsByName;
private final Map<String, DataGenerator> dataGeneratorsByName;
@SuppressWarnings("unused")
private final Application application;
/** Derived expaned map for fast lookup */
private Map<String, Map<String, DataExtractor>> dataExtractors = new HashMap<String, Map<String, DataExtractor>>(11);
/** Derived expaned map for fast lookup */
private Map<String, Map<String, DataGenerator>> dataGenerators = new HashMap<String, Map<String, DataGenerator>>(11);
/** Derived expaned map for fast lookup */
private Map<String, DataGeneratorScope> dataGeneratorScopes = new HashMap<String, DataGeneratorScope>(11);
/**
* @param application the application that will be wrapped
* @param dataExtractorsByName data extractors to use
* @param dataGeneratorsByName data generators to use
*/
/* package */ AuditApplication(
Map<String, DataExtractor> dataExtractorsByName,
Map<String, DataGenerator> dataGeneratorsByName,
Application application)
{
this.dataExtractorsByName = dataExtractorsByName;
this.dataGeneratorsByName = dataGeneratorsByName;
this.application = application;
this.applicationName = application.getName();
this.applicationKey = application.getKey();
buildAuditPaths(application);
}
@Override
public int hashCode()
{
return applicationName.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj instanceof AuditApplication)
{
AuditApplication that = (AuditApplication) obj;
return this.applicationName.equals(that.applicationName);
}
else
{
return false;
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(56);
sb.append("AuditApplication")
.append("[ name=").append(applicationName)
.append("]");
return sb.toString();
}
/**
* Get the application name
*/
public String getApplicationName()
{
return applicationName;
}
/**
* Get the key (root path) for the application
*/
public String getApplicationKey()
{
return applicationKey;
}
/**
* Helper method to check that a path is correct for this application instance
*
* @param path the path in format <b>/app-key/x/y/z</b>
* @throws AuditModelException if the path is invalid
*/
public void checkPath(String path)
{
if (path == null || path.length() == 0)
{
generateException(path, "Invalid audit path.");
}
if (!path.startsWith(AUDIT_PATH_SEPARATOR))
{
generateException(
path,
"An audit path must always start with the separator '" + AUDIT_PATH_SEPARATOR + "'.");
}
if (path.indexOf(applicationKey, 0) != 1)
{
generateException(
path,
"An audit path's first element must be the application's key i.e. '" + applicationKey + "'.");
}
if (path.endsWith(AUDIT_PATH_SEPARATOR))
{
generateException(
path,
"An audit path may not end with the separator '" + AUDIT_PATH_SEPARATOR + "'.");
}
if (!path.toLowerCase().equals(path))
{
generateException(
path,
"An audit path may only contain lowercase letters, '-' and '.' i.e. regex ([a-z]|[0-9]|\\-|\\.)*");
}
}
/**
* Get all data extractors applicable to a given path and scope.
*
* @param path the audit path
* @return Returns all data extractors mapped to their key-path
*/
public Map<String, DataExtractor> getDataExtractors(String path)
{
Map<String, DataExtractor> extractors = dataExtractors.get(path);
if (extractors == null)
{
// Don't give back a null
extractors = Collections.emptyMap();
}
else
{
// we don't want to give back a modifiable map
extractors = Collections.unmodifiableMap(extractors);
}
// Done
if (logger.isDebugEnabled())
{
logger.debug(
"Looked up data extractors: \n" +
" Path: " + path + "\n" +
" Found: " + extractors);
}
return extractors;
}
/**
* Get all data generators applicable to a given path and scope.
*
* @param path the audit path
* @param scope the audit scope (e.g. SESSION or AUDIT)
* @return Returns all data generators mapped to their key-path
*/
public Map<String, DataGenerator> getDataGenerators(String path, DataGeneratorScope scope)
{
Map<String, DataGenerator> generators = dataGenerators.get(path);
if (generators == null)
{
// Don't give back a null
generators = new HashMap<String, DataGenerator>(0);
}
else
{
// Copy the map so that (a) we can modify it during iteration and (b) we return
// something that the client can't mess up.
generators = new HashMap<String, DataGenerator>(generators);
}
if (scope != DataGeneratorScope.ALL)
{
// Go through them and eliminate the ones in the wrong scope
Iterator<Map.Entry<String, DataGenerator>> iterator = generators.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, DataGenerator> entry = iterator.next();
String generatorPath = entry.getKey();
DataGeneratorScope generatorScope = dataGeneratorScopes.get(generatorPath);
if (generatorScope == DataGeneratorScope.ALL)
{
// This one always applies
continue;
}
else if (generatorScope != scope)
{
// Wrong scope
iterator.remove();
}
}
}
// Done
if (logger.isDebugEnabled())
{
logger.debug(
"Looked up data generators: \n" +
" Path: " + path + "\n" +
" Scope: " + scope + "\n" +
" Found: " + generators);
}
return generators;
}
/**
* Internal helper method to kick off generator and extractor path mappings
*/
private void buildAuditPaths(AuditPath auditPath)
{
buildAuditPaths(
auditPath,
"",
new HashSet<String>(37),
new HashMap<String, DataExtractor>(13),
new HashMap<String, DataGenerator>(13));
}
/**
* Recursive method to build generator and extractor mappings
*/
private void buildAuditPaths(
AuditPath auditPath,
String currentPath,
Set<String> existingPaths,
Map<String, DataExtractor> upperExtractorsByPath,
Map<String, DataGenerator> upperGeneratorsByPath)
{
// Clone the upper maps to prevent pollution
upperExtractorsByPath = new HashMap<String, DataExtractor>(upperExtractorsByPath);
upperGeneratorsByPath = new HashMap<String, DataGenerator>(upperGeneratorsByPath);
// Append the current audit path to the current path
currentPath = (currentPath + AUDIT_PATH_SEPARATOR + auditPath.getKey());
// Make sure we have not processed it before
if (!existingPaths.add(currentPath))
{
generateException(currentPath, "The audit path already exists.");
}
// Make sure that the path is all lowercase
checkPathCase(currentPath);
// Get the data extractors declared for this key
for (RecordValue element : auditPath.getRecordValue())
{
String key = element.getKey();
String extractorPath = (currentPath + AUDIT_PATH_SEPARATOR + key);
if (!existingPaths.add(extractorPath))
{
generateException(extractorPath, "The audit path already exists.");
}
// Make sure that the path is all lowercase
checkPathCase(extractorPath);
String extractorName = element.getDataExtractor();
DataExtractor extractor = dataExtractorsByName.get(extractorName);
if (extractor == null)
{
generateException(extractorPath, "No data extractor exists for name: " + extractorName);
}
// All generators that occur earlier in the path will also be applicable here
upperExtractorsByPath.put(extractorPath, extractor);
}
// All the extractors apply to the current path
dataExtractors.put(currentPath, upperExtractorsByPath);
// Get the data generators declared for this key
for (GenerateValue element : auditPath.getGenerateValue())
{
String key = element.getKey();
String generatorPath = (currentPath + AUDIT_PATH_SEPARATOR + key);
if (!existingPaths.add(generatorPath))
{
generateException(generatorPath, "The audit path already exists.");
}
// Make sure that the path is all lowercase
checkPathCase(generatorPath);
String generatorName = element.getDataGenerator();
DataGenerator generator = dataGeneratorsByName.get(generatorName);
if (generator == null)
{
generateException(generatorPath, "No data generator exists for name: " + generatorName);
}
// Store the scope
ScopeAttribute scopeAttribute = element.getScope();
if (scopeAttribute == null)
{
generateException(generatorPath, "No scope defined for generator: " + generatorName);
}
String scopeStr = scopeAttribute.value();
if (scopeStr == null)
{
scopeStr = DataGeneratorScope.AUDIT.toString();
}
try
{
DataGeneratorScope scope = DataGeneratorScope.valueOf(scopeStr);
dataGeneratorScopes.put(generatorPath, scope);
}
catch (Throwable e)
{
generateException(generatorPath, "Illegal generator scope value: " + scopeStr);
}
// All generators that occur earlier in the path will also be applicable here
upperGeneratorsByPath.put(generatorPath, generator);
}
// All the generators apply to the current path
dataGenerators.put(currentPath, upperGeneratorsByPath);
// Find all sub audit paths and recurse
for (AuditPath element : auditPath.getAuditPath())
{
buildAuditPaths(element, currentPath, existingPaths, upperExtractorsByPath, upperGeneratorsByPath);
}
}
private void checkPathCase(String path)
{
if (!path.equals(path.toLowerCase()))
{
generateException(path, "Audit key entries may only be in lowercase to ensure case-insensitivity.");
}
}
private void generateException(String path, String msg) throws AuditModelException
{
throw new AuditModelException("" +
msg + "\n" +
" Application: " + applicationName + "\n" +
" Path: " + path);
}
}

View File

@@ -25,8 +25,6 @@
package org.alfresco.repo.audit.model; package org.alfresco.repo.audit.model;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
@@ -41,16 +39,29 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.ValidationEventLocator;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.repo.audit.extractor.DataExtractor; import org.alfresco.repo.audit.extractor.DataExtractor;
import org.alfresco.repo.audit.generator.DataGenerator; import org.alfresco.repo.audit.generator.DataGenerator;
import org.alfresco.repo.audit.model._3.Application; import org.alfresco.repo.audit.model._3.Application;
import org.alfresco.repo.audit.model._3.Audit; import org.alfresco.repo.audit.model._3.Audit;
import org.alfresco.repo.audit.model._3.DataExtractors; import org.alfresco.repo.audit.model._3.DataExtractors;
import org.alfresco.repo.audit.model._3.DataGenerators; import org.alfresco.repo.audit.model._3.DataGenerators;
import org.alfresco.repo.audit.model._3.ObjectFactory;
import org.alfresco.repo.domain.audit.AuditDAO; import org.alfresco.repo.domain.audit.AuditDAO;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ResourceUtils;
import org.xml.sax.SAXParseException;
/** /**
* Component used to store audit model definitions. It ensures that duplicate application and converter * Component used to store audit model definitions. It ensures that duplicate application and converter
@@ -61,8 +72,13 @@ import org.alfresco.service.cmr.repository.NodeRef;
*/ */
public class AuditModelRegistry public class AuditModelRegistry
{ {
public static final String AUDIT_SCHEMA_LOCATION = "classpath:alfresco/audit/alfresco-audit-3.2.xsd";
private TransactionService transactionService;
private AuditDAO auditDAO; private AuditDAO auditDAO;
private static final Log logger = LogFactory.getLog(AuditModelRegistry.class);
private final ReentrantReadWriteLock.ReadLock readLock; private final ReentrantReadWriteLock.ReadLock readLock;
private final ReentrantReadWriteLock.WriteLock writeLock; private final ReentrantReadWriteLock.WriteLock writeLock;
@@ -73,7 +89,7 @@ public class AuditModelRegistry
/** /**
* Used to lookup the audit application java hierarchy * Used to lookup the audit application java hierarchy
*/ */
private final Map<String, Application> auditApplicationsByName; private final Map<String, AuditApplication> auditApplicationsByName;
/** /**
* Used to lookup a reference to the persisted config binary for an application * Used to lookup a reference to the persisted config binary for an application
*/ */
@@ -92,10 +108,18 @@ public class AuditModelRegistry
auditModels = new ArrayList<Audit>(7); auditModels = new ArrayList<Audit>(7);
dataExtractorsByName = new HashMap<String, DataExtractor>(13); dataExtractorsByName = new HashMap<String, DataExtractor>(13);
dataGeneratorsByName = new HashMap<String, DataGenerator>(13); dataGeneratorsByName = new HashMap<String, DataGenerator>(13);
auditApplicationsByName = new HashMap<String, Application>(7); auditApplicationsByName = new HashMap<String, AuditApplication>(7);
auditModelIdsByApplicationsName = new HashMap<String, Long>(7); auditModelIdsByApplicationsName = new HashMap<String, Long>(7);
} }
/**
* Service to ensure DAO calls are transactionally wrapped
*/
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
/** /**
* Set the DAO used to persisted the registered audit models * Set the DAO used to persisted the registered audit models
*/ */
@@ -116,8 +140,7 @@ public class AuditModelRegistry
{ {
if (auditModelUrls.contains(auditModelUrl)) if (auditModelUrls.contains(auditModelUrl))
{ {
throw new AlfrescoRuntimeException( logger.warn("An audit model has already been registered at URL " + auditModelUrl);
"An audit model has already been registered at URL " + auditModelUrl);
} }
auditModelUrls.add(auditModelUrl); auditModelUrls.add(auditModelUrl);
} }
@@ -145,26 +168,67 @@ public class AuditModelRegistry
} }
} }
/**
* Cleans out all derived data
*/
private void clearCaches()
{
auditModels.clear();
dataExtractorsByName.clear();
dataGeneratorsByName.clear();;
auditApplicationsByName.clear();
auditModelIdsByApplicationsName.clear();
}
/** /**
* Method to load audit models into memory. This method is also responsible for persisting * Method to load audit models into memory. This method is also responsible for persisting
* the audit models for later retrieval. Models are loaded from the locations given by the * the audit models for later retrieval. Models are loaded from the locations given by the
* {@link #registerModel(URL) register} methods. * {@link #registerModel(URL) register} methods.
* <p/>
* Note, the models are loaded in a new transaction.
*/ */
public void loadAuditModels() public void loadAuditModels()
{ {
writeLock.lock(); RetryingTransactionCallback<Void> loadModelsCallback = new RetryingTransactionCallback<Void>()
try {
public Void execute() throws Throwable
{ {
// Load models from the URLs // Load models from the URLs
for (URL auditModelUrl : auditModelUrls) Set<URL> auditModelUrlsInner = new HashSet<URL>(auditModelUrls);
for (URL auditModelUrl : auditModelUrlsInner)
{ {
Audit audit = AuditModelRegistry.unmarshallModel(auditModelUrl); Audit audit = AuditModelRegistry.unmarshallModel(auditModelUrl);
// That worked, so now get an input stream and write the model // That worked, so now get an input stream and write the model
Long auditModelId = auditDAO.getOrCreateAuditModel(auditModelUrl).getFirst(); Long auditModelId = auditDAO.getOrCreateAuditModel(auditModelUrl).getFirst();
try
{
// Now cache it (eagerly) // Now cache it (eagerly)
cacheAuditElements(auditModelId, audit); cacheAuditElements(auditModelId, audit);
} }
catch (Throwable e)
{
// Mainly for test purposes, we clear out the failed URL
auditModelUrls.remove(auditModelUrl);
clearCaches();
throw new AuditModelException(
"Failed to load audit model: " + auditModelUrl + "\n" +
e.getMessage());
}
}
// NOTE: If we support other types of loading, then that will have to go here, too // NOTE: If we support other types of loading, then that will have to go here, too
// Done
return null;
}
};
writeLock.lock();
// Drop all cached data
clearCaches();
try
{
transactionService.getRetryingTransactionHelper().doInTransaction(loadModelsCallback);
} }
finally finally
{ {
@@ -197,7 +261,7 @@ public class AuditModelRegistry
* @param application the name of the audited application * @param application the name of the audited application
* @return the java model (<tt>null</tt> if not found) * @return the java model (<tt>null</tt> if not found)
*/ */
public Application getAuditApplication(String application) public AuditApplication getAuditApplication(String application)
{ {
readLock.lock(); readLock.lock();
try try
@@ -219,14 +283,8 @@ public class AuditModelRegistry
{ {
try try
{ {
File file = new File(configUrl.getFile());
if (!file.exists())
{
throw new AlfrescoRuntimeException("The Audit model XML was not found: " + configUrl);
}
// Load it // Load it
InputStream is = new BufferedInputStream(new FileInputStream(file)); InputStream is = new BufferedInputStream(configUrl.openStream());
return unmarshallModel(is, configUrl.toString()); return unmarshallModel(is, configUrl.toString());
} }
catch (IOException e) catch (IOException e)
@@ -238,25 +296,61 @@ public class AuditModelRegistry
/** /**
* Unmarshalls the Audit model from a stream * Unmarshalls the Audit model from a stream
*/ */
private static Audit unmarshallModel(InputStream is, String source) private static Audit unmarshallModel(InputStream is, final String source)
{ {
final Schema schema;
final JAXBContext jaxbCtx;
final Unmarshaller jaxbUnmarshaller;
try try
{ {
JAXBContext jaxbCtx = JAXBContext.newInstance("org.alfresco.repo.audit.model._3"); SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
Unmarshaller jaxbUnmarshaller = jaxbCtx.createUnmarshaller(); schema = sf.newSchema(ResourceUtils.getURL(AUDIT_SCHEMA_LOCATION));
jaxbCtx = JAXBContext.newInstance("org.alfresco.repo.audit.model._3");
jaxbUnmarshaller = jaxbCtx.createUnmarshaller();
jaxbUnmarshaller.setSchema(schema);
jaxbUnmarshaller.setEventHandler(new ValidationEventHandler()
{
public boolean handleEvent(ValidationEvent ve)
{
if (ve.getSeverity() == ValidationEvent.FATAL_ERROR || ve.getSeverity() == ValidationEvent.ERROR)
{
ValidationEventLocator locator = ve.getLocator();
logger.error("Invalid Audit XML: \n" +
" Source: " + source + "\n" +
" Location: Line " + locator.getLineNumber() + " column " + locator.getColumnNumber() + "\n" +
" Error: " + ve.getMessage());
}
return true;
}
});
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException(
"Failed to load Alfresco Audit Schema from " + AUDIT_SCHEMA_LOCATION, e);
}
try
{
// Unmarshall with validation
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
JAXBElement<Audit> auditElement = (JAXBElement<Audit>) jaxbUnmarshaller.unmarshal(is); JAXBElement<Audit> auditElement = (JAXBElement<Audit>) jaxbUnmarshaller.unmarshal(is);
Audit audit = auditElement.getValue(); Audit audit = auditElement.getValue();
// Done // Done
return audit; return audit;
} }
catch (Throwable e) catch (Throwable e)
{ {
throw new AlfrescoRuntimeException( // Dig out a SAXParseException, if there is one
Throwable saxError = ExceptionStackUtil.getCause(e, SAXParseException.class);
if (saxError != null)
{
e = saxError;
}
throw new AuditModelException(
"Failed to read Audit model XML: \n" + "Failed to read Audit model XML: \n" +
" Source: " + source + "\n" + " Source: " + source + "\n" +
" Error: " + e.getMessage(), " Error: " + e.getMessage());
e);
} }
finally finally
{ {
@@ -268,14 +362,14 @@ public class AuditModelRegistry
{ {
// Get the data extractors and check for duplicates // Get the data extractors and check for duplicates
DataExtractors extractorsElement = audit.getDataExtractors(); DataExtractors extractorsElement = audit.getDataExtractors();
if (extractorsElement == null)
{
extractorsElement = new ObjectFactory().createDataExtractors();
}
List<org.alfresco.repo.audit.model._3.DataExtractor> converterElements = extractorsElement.getDataExtractor(); List<org.alfresco.repo.audit.model._3.DataExtractor> converterElements = extractorsElement.getDataExtractor();
for (org.alfresco.repo.audit.model._3.DataExtractor converterElement : converterElements) for (org.alfresco.repo.audit.model._3.DataExtractor converterElement : converterElements)
{ {
String name = converterElement.getName(); String name = converterElement.getName();
if (dataExtractorsByName.containsKey(name))
{
throw new AuditModelException("Audit data extractor '" + name + "' has already been defined.");
}
// Construct the converter // Construct the converter
final DataExtractor dataExtractor; final DataExtractor dataExtractor;
try try
@@ -293,18 +387,29 @@ public class AuditModelRegistry
throw new AuditModelException( throw new AuditModelException(
"Audit data extractor '" + name + "' could not be constructed: " + converterElement.getClazz()); "Audit data extractor '" + name + "' could not be constructed: " + converterElement.getClazz());
} }
// If the name is taken, make sure that they are equal
if (dataExtractorsByName.containsKey(name))
{
DataExtractor existing = dataExtractorsByName.get(name);
if (!existing.equals(dataExtractor))
{
throw new AuditModelException(
"Audit data extractor '" + name + "' is incompatible with an existing instance.");
}
}
// Store
dataExtractorsByName.put(name, dataExtractor); dataExtractorsByName.put(name, dataExtractor);
} }
// Get the data generators and check for duplicates // Get the data generators and check for duplicates
DataGenerators generatorsElement = audit.getDataGenerators(); DataGenerators generatorsElement = audit.getDataGenerators();
if (generatorsElement == null)
{
generatorsElement = new ObjectFactory().createDataGenerators();
}
List<org.alfresco.repo.audit.model._3.DataGenerator> generatorElements = generatorsElement.getDataGenerator(); List<org.alfresco.repo.audit.model._3.DataGenerator> generatorElements = generatorsElement.getDataGenerator();
for (org.alfresco.repo.audit.model._3.DataGenerator generatorElement : generatorElements) for (org.alfresco.repo.audit.model._3.DataGenerator generatorElement : generatorElements)
{ {
String name = generatorElement.getName(); String name = generatorElement.getName();
if (dataGeneratorsByName.containsKey(name))
{
throw new AuditModelException("Audit data generator '" + name + "' has already been defined.");
}
// Construct the converter // Construct the converter
final DataGenerator dataGenerator; final DataGenerator dataGenerator;
try try
@@ -322,6 +427,17 @@ public class AuditModelRegistry
throw new AuditModelException( throw new AuditModelException(
"Audit data generator '" + name + "' could not be constructed: " + generatorElement.getClazz()); "Audit data generator '" + name + "' could not be constructed: " + generatorElement.getClazz());
} }
// If the name is taken, make sure that they are equal
if (dataGeneratorsByName.containsKey(name))
{
DataGenerator existing = dataGeneratorsByName.get(name);
if (!existing.equals(dataGenerator))
{
throw new AuditModelException(
"Audit data generator '" + name + "' is incompatible with an existing instance.");
}
}
// Store
dataGeneratorsByName.put(name, dataGenerator); dataGeneratorsByName.put(name, dataGenerator);
} }
// Get the application and check for duplicates // Get the application and check for duplicates
@@ -333,7 +449,8 @@ public class AuditModelRegistry
{ {
throw new AuditModelException("Audit application '" + name + "' has already been defined."); throw new AuditModelException("Audit application '" + name + "' has already been defined.");
} }
auditApplicationsByName.put(name, application); AuditApplication wrapperApp = new AuditApplication(dataExtractorsByName, dataGeneratorsByName, application);
auditApplicationsByName.put(name, wrapperApp);
auditModelIdsByApplicationsName.put(name, auditModelId); auditModelIdsByApplicationsName.put(name, auditModelId);
} }
// Store the model itself // Store the model itself

View File

@@ -20,7 +20,6 @@ import javax.xml.bind.annotation.XmlType;
* &lt;complexContent> * &lt;complexContent>
* &lt;extension base="{http://www.alfresco.org/repo/audit/model/3.2}KeyedAuditDefinition"> * &lt;extension base="{http://www.alfresco.org/repo/audit/model/3.2}KeyedAuditDefinition">
* &lt;sequence> * &lt;sequence>
* &lt;element name="AuditSession" type="{http://www.alfresco.org/repo/audit/model/3.2}AuditSession" minOccurs="0"/>
* &lt;element name="RecordValue" type="{http://www.alfresco.org/repo/audit/model/3.2}RecordValue" maxOccurs="unbounded" minOccurs="0"/> * &lt;element name="RecordValue" type="{http://www.alfresco.org/repo/audit/model/3.2}RecordValue" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element name="GenerateValue" type="{http://www.alfresco.org/repo/audit/model/3.2}GenerateValue" maxOccurs="unbounded" minOccurs="0"/> * &lt;element name="GenerateValue" type="{http://www.alfresco.org/repo/audit/model/3.2}GenerateValue" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element name="AuditPath" type="{http://www.alfresco.org/repo/audit/model/3.2}AuditPath" maxOccurs="unbounded" minOccurs="0"/> * &lt;element name="AuditPath" type="{http://www.alfresco.org/repo/audit/model/3.2}AuditPath" maxOccurs="unbounded" minOccurs="0"/>
@@ -34,7 +33,6 @@ import javax.xml.bind.annotation.XmlType;
*/ */
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AuditPath", propOrder = { @XmlType(name = "AuditPath", propOrder = {
"auditSession",
"recordValue", "recordValue",
"generateValue", "generateValue",
"auditPath" "auditPath"
@@ -46,8 +44,6 @@ public class AuditPath
extends KeyedAuditDefinition extends KeyedAuditDefinition
{ {
@XmlElement(name = "AuditSession")
protected AuditSession auditSession;
@XmlElement(name = "RecordValue") @XmlElement(name = "RecordValue")
protected List<RecordValue> recordValue; protected List<RecordValue> recordValue;
@XmlElement(name = "GenerateValue") @XmlElement(name = "GenerateValue")
@@ -55,30 +51,6 @@ public class AuditPath
@XmlElement(name = "AuditPath") @XmlElement(name = "AuditPath")
protected List<AuditPath> auditPath; protected List<AuditPath> auditPath;
/**
* Gets the value of the auditSession property.
*
* @return
* possible object is
* {@link AuditSession }
*
*/
public AuditSession getAuditSession() {
return auditSession;
}
/**
* Sets the value of the auditSession property.
*
* @param value
* allowed object is
* {@link AuditSession }
*
*/
public void setAuditSession(AuditSession value) {
this.auditSession = value;
}
/** /**
* Gets the value of the recordValue property. * Gets the value of the recordValue property.
* *

View File

@@ -1,69 +0,0 @@
package org.alfresco.repo.audit.model._3;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for AuditSession complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType name="AuditSession">
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="GenerateValue" type="{http://www.alfresco.org/repo/audit/model/3.2}GenerateValue" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AuditSession", propOrder = {
"generateValue"
})
public class AuditSession {
@XmlElement(name = "GenerateValue")
protected List<GenerateValue> generateValue;
/**
* Gets the value of the generateValue property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the generateValue property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getGenerateValue().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link GenerateValue }
*
*
*/
public List<GenerateValue> getGenerateValue() {
if (generateValue == null) {
generateValue = new ArrayList<GenerateValue>();
}
return this.generateValue;
}
}

View File

@@ -17,6 +17,7 @@ import javax.xml.bind.annotation.XmlType;
* &lt;complexContent> * &lt;complexContent>
* &lt;extension base="{http://www.alfresco.org/repo/audit/model/3.2}KeyedAuditDefinition"> * &lt;extension base="{http://www.alfresco.org/repo/audit/model/3.2}KeyedAuditDefinition">
* &lt;attribute name="dataGenerator" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}NameAttribute" /> * &lt;attribute name="dataGenerator" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}NameAttribute" />
* &lt;attribute name="scope" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}ScopeAttribute" />
* &lt;/extension> * &lt;/extension>
* &lt;/complexContent> * &lt;/complexContent>
* &lt;/complexType> * &lt;/complexType>
@@ -32,6 +33,8 @@ public class GenerateValue
@XmlAttribute(required = true) @XmlAttribute(required = true)
protected String dataGenerator; protected String dataGenerator;
@XmlAttribute(required = true)
protected ScopeAttribute scope;
/** /**
* Gets the value of the dataGenerator property. * Gets the value of the dataGenerator property.
@@ -57,4 +60,28 @@ public class GenerateValue
this.dataGenerator = value; this.dataGenerator = value;
} }
/**
* Gets the value of the scope property.
*
* @return
* possible object is
* {@link ScopeAttribute }
*
*/
public ScopeAttribute getScope() {
return scope;
}
/**
* Sets the value of the scope property.
*
* @param value
* allowed object is
* {@link ScopeAttribute }
*
*/
public void setScope(ScopeAttribute value) {
this.scope = value;
}
} }

View File

@@ -33,6 +33,30 @@ public class ObjectFactory {
public ObjectFactory() { public ObjectFactory() {
} }
/**
* Create an instance of {@link KeyedAuditDefinition }
*
*/
public KeyedAuditDefinition createKeyedAuditDefinition() {
return new KeyedAuditDefinition();
}
/**
* Create an instance of {@link DataExtractor }
*
*/
public DataExtractor createDataExtractor() {
return new DataExtractor();
}
/**
* Create an instance of {@link Audit }
*
*/
public Audit createAudit() {
return new Audit();
}
/** /**
* Create an instance of {@link DataExtractors } * Create an instance of {@link DataExtractors }
* *
@@ -49,6 +73,14 @@ public class ObjectFactory {
return new AuditPath(); return new AuditPath();
} }
/**
* Create an instance of {@link DataGenerator }
*
*/
public DataGenerator createDataGenerator() {
return new DataGenerator();
}
/** /**
* Create an instance of {@link DataGenerators } * Create an instance of {@link DataGenerators }
* *
@@ -57,14 +89,6 @@ public class ObjectFactory {
return new DataGenerators(); return new DataGenerators();
} }
/**
* Create an instance of {@link KeyedAuditDefinition }
*
*/
public KeyedAuditDefinition createKeyedAuditDefinition() {
return new KeyedAuditDefinition();
}
/** /**
* Create an instance of {@link RecordValue } * Create an instance of {@link RecordValue }
* *
@@ -73,38 +97,6 @@ public class ObjectFactory {
return new RecordValue(); return new RecordValue();
} }
/**
* Create an instance of {@link DataGenerator }
*
*/
public DataGenerator createDataGenerator() {
return new DataGenerator();
}
/**
* Create an instance of {@link AuditSession }
*
*/
public AuditSession createAuditSession() {
return new AuditSession();
}
/**
* Create an instance of {@link GenerateValue }
*
*/
public GenerateValue createGenerateValue() {
return new GenerateValue();
}
/**
* Create an instance of {@link DataExtractor }
*
*/
public DataExtractor createDataExtractor() {
return new DataExtractor();
}
/** /**
* Create an instance of {@link Application } * Create an instance of {@link Application }
* *
@@ -114,11 +106,11 @@ public class ObjectFactory {
} }
/** /**
* Create an instance of {@link Audit } * Create an instance of {@link GenerateValue }
* *
*/ */
public Audit createAudit() { public GenerateValue createGenerateValue() {
return new Audit(); return new GenerateValue();
} }
/** /**

View File

@@ -0,0 +1,40 @@
package org.alfresco.repo.audit.model._3;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for ScopeAttribute.
*
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* &lt;simpleType name="ScopeAttribute">
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string">
* &lt;enumeration value="SESSION"/>
* &lt;enumeration value="AUDIT"/>
* &lt;enumeration value="ALL"/>
* &lt;/restriction>
* &lt;/simpleType>
* </pre>
*
*/
@XmlType(name = "ScopeAttribute")
@XmlEnum
public enum ScopeAttribute {
SESSION,
AUDIT,
ALL;
public String value() {
return name();
}
public static ScopeAttribute fromValue(String v) {
return valueOf(v);
}
}

View File

@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Default Audit Configuration -->
<Audit
xmlns="http://www.alfresco.org/repo/audit/model/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
>
<Application name="Alfresco Test Bad 01" key="test-bad-01">
<AuditPath key="1.1">
<AuditPath key="2.1">
<RecordValue key="value.1" dataExtractor="blah"/>
</AuditPath>
</AuditPath>
</Application>
</Audit>

View File

@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Default Audit Configuration -->
<Audit
xmlns="http://www.alfresco.org/repo/audit/model/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
>
<Application name="Alfresco Test Bad 02" key="test-bad-02">
<AuditPath key="1.1">
<AuditPath key="2.1">
<GenerateValue key="value.1" dataGenerator="blah" scope="AUDIT"/>
</AuditPath>
</AuditPath>
</Application>
</Audit>

View File

@@ -0,0 +1,22 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Default Audit Configuration -->
<Audit
xmlns="http://www.alfresco.org/repo/audit/model/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
>
<Application name="Alfresco Test Bad 03" key="test-bad-03">
<AuditPath key="1.1">
<AuditPath key="2.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
</AuditPath>
<AuditPath key="2.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
</AuditPath>
</AuditPath>
</Application>
</Audit>

View File

@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Default Audit Configuration -->
<Audit
xmlns="http://www.alfresco.org/repo/audit/model/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
>
<Application name="Alfresco Test Bad 04" key="test-bad-04">
<AuditPath key="1.1">
<AuditPath key="2.1">
<RecordValue key="value.UPPER" dataExtractor="simpleValue"/>
</AuditPath>
</AuditPath>
</Application>
</Audit>

View File

@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Default Audit Configuration -->
<Audit
xmlns="http://www.alfresco.org/repo/audit/model/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
>
<Application name="Alfresco Test Bad 05" key="test-bad-05">
<AuditPath key="1.1">
<AuditPath key="2.1">
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSIOn"/>
</AuditPath>
</AuditPath>
</Application>
</Audit>

View File

@@ -0,0 +1,66 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Default Audit Configuration -->
<Audit
xmlns="http://www.alfresco.org/repo/audit/model/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
>
<!-- Includes duplicate definitions of the extractors and generators -->
<DataExtractors>
<DataExtractor name="simpleValue" class="org.alfresco.repo.audit.extractor.SimpleValueDataExtractor"/>
</DataExtractors>
<DataGenerators>
<DataGenerator name="systemTime" class="org.alfresco.repo.audit.generator.SystemTimeDataGenerator"/>
</DataGenerators>
<Application name="Alfresco Test" key="test">
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSION"/>
<AuditPath key="1.1">
<AuditPath key="2.1">
<AuditPath key="3.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
<AuditPath key="4.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
</AuditPath>
<AuditPath key="4.2">
<RecordValue key="value.2" dataExtractor="simpleValue"/>
</AuditPath>
</AuditPath>
<AuditPath key="3.2">
<AuditPath key="4.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
<GenerateValue key="time" dataGenerator="systemTime" scope="AUDIT"/>
</AuditPath>
<AuditPath key="4.2">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
</AuditPath>
</AuditPath>
</AuditPath>
<AuditPath key="2.2">
<AuditPath key="3.1">
<AuditPath key="4.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
</AuditPath>
<AuditPath key="4.2">
<RecordValue key="value.2" dataExtractor="simpleValue"/>
</AuditPath>
</AuditPath>
<AuditPath key="3.2">
<AuditPath key="4.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
<GenerateValue key="time" dataGenerator="systemTime" scope="ALL"/>
</AuditPath>
<AuditPath key="4.2">
<RecordValue key="value.1" dataExtractor="simpleValue"/>
</AuditPath>
</AuditPath>
</AuditPath>
</AuditPath>
</Application>
</Audit>