Audit changes and fixes

- Removed notion of audit session
 - Removed 'scope' attribute for DataGenerator elements
 - Removed alf_audit_session table and replaced with alf_audit_app (see script)
 - DataGenerators are working properly


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@16053 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-09-02 14:45:23 +00:00
parent 381d7730c5
commit 2cdc1777f9
30 changed files with 448 additions and 515 deletions

View File

@@ -79,7 +79,6 @@
<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>
@@ -114,12 +113,4 @@
</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

@@ -22,7 +22,7 @@
<Application name="Alfresco Repository" key="repository"> <Application name="Alfresco Repository" key="repository">
<AuditPath key="services"> <AuditPath key="services">
<GenerateValue key="txn" dataGenerator="transactionId" scope="AUDIT"/> <GenerateValue key="txn" dataGenerator="transactionId"/>
<AuditPath key="nodeservice"> <AuditPath key="nodeservice">
<AuditPath key="createstore"> <AuditPath key="createstore">
<AuditPath key="protocol"> <AuditPath key="protocol">

View File

@@ -17,24 +17,24 @@ CREATE TABLE alf_audit_model
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE alf_audit_session CREATE TABLE alf_audit_app
( (
id BIGINT NOT NULL AUTO_INCREMENT, id BIGINT NOT NULL AUTO_INCREMENT,
audit_model_id BIGINT NOT NULL, audit_model_id BIGINT NOT NULL,
app_name_id BIGINT NOT NULL, app_name_id BIGINT NOT NULL,
CONSTRAINT fk_alf_audit_sess_model FOREIGN KEY (audit_model_id) REFERENCES alf_audit_model (id), CONSTRAINT fk_alf_audit_app_model FOREIGN KEY (audit_model_id) REFERENCES alf_audit_model (id) ON DELETE CASCADE,
CONSTRAINT fk_alf_audit_sess_app FOREIGN KEY (app_name_id) REFERENCES alf_prop_value (id), CONSTRAINT fk_alf_audit_app_app FOREIGN KEY (app_name_id) REFERENCES alf_prop_value (id),
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE alf_audit_entry CREATE TABLE alf_audit_entry
( (
id BIGINT NOT NULL AUTO_INCREMENT, id BIGINT NOT NULL AUTO_INCREMENT,
audit_session_id BIGINT NOT NULL, audit_app_id BIGINT NOT NULL,
audit_time BIGINT NOT NULL, audit_time BIGINT NOT NULL,
audit_user_id BIGINT NULL, audit_user_id BIGINT NULL,
audit_values_id BIGINT NULL, audit_values_id BIGINT NULL,
CONSTRAINT fk_alf_audit_ent_sess FOREIGN KEY (audit_session_id) REFERENCES alf_audit_session (id), CONSTRAINT fk_alf_audit_ent_app FOREIGN KEY (audit_app_id) REFERENCES alf_audit_app (id) ON DELETE CASCADE,
INDEX idx_alf_audit_ent_time (audit_time), INDEX idx_alf_audit_ent_time (audit_time),
CONSTRAINT fk_alf_audit_ent_user FOREIGN KEY (audit_user_id) REFERENCES alf_prop_value (id), CONSTRAINT fk_alf_audit_ent_user FOREIGN KEY (audit_user_id) REFERENCES alf_prop_value (id),
CONSTRAINT fk_alf_audit_ent_prop FOREIGN KEY (audit_values_id) REFERENCES alf_prop_value (id), CONSTRAINT fk_alf_audit_ent_prop FOREIGN KEY (audit_values_id) REFERENCES alf_prop_value (id),

View File

@@ -11,7 +11,7 @@
<!-- --> <!-- -->
<typeAlias alias="AuditModel" type="org.alfresco.repo.domain.audit.AuditModelEntity"/> <typeAlias alias="AuditModel" type="org.alfresco.repo.domain.audit.AuditModelEntity"/>
<typeAlias alias="AuditSession" type="org.alfresco.repo.domain.audit.AuditSessionEntity"/> <typeAlias alias="AuditApplication" type="org.alfresco.repo.domain.audit.AuditApplicationEntity"/>
<typeAlias alias="AuditEntry" type="org.alfresco.repo.domain.audit.AuditEntryEntity"/> <typeAlias alias="AuditEntry" type="org.alfresco.repo.domain.audit.AuditEntryEntity"/>
<!-- --> <!-- -->
@@ -23,14 +23,14 @@
<result property="contentDataId" column="content_data_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="contentDataId" column="content_data_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="contentCrc" column="content_crc" jdbcType="BIGINT" javaType="long"/> <result property="contentCrc" column="content_crc" jdbcType="BIGINT" javaType="long"/>
</resultMap> </resultMap>
<resultMap id="result.AuditSession" class="AuditSession"> <resultMap id="result.AuditApplication" class="AuditApplication">
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="auditModelId" column="audit_model_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="auditModelId" column="audit_model_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="applicationNameId" column="app_name_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="applicationNameId" column="app_name_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
</resultMap> </resultMap>
<resultMap id="result.AuditEntry" class="AuditEntry"> <resultMap id="result.AuditEntry" class="AuditEntry">
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="auditSessionId" column="audit_session_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="auditApplicationId" column="audit_app_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="auditUserId" column="audit_user_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="auditUserId" column="audit_user_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="auditTime" column="audit_time" jdbcType="BIGINT" javaType="long"/> <result property="auditTime" column="audit_time" jdbcType="BIGINT" javaType="long"/>
<result property="auditValuesId" column="audit_values_id" jdbcType="BIGINT" javaType="long"/> <result property="auditValuesId" column="audit_values_id" jdbcType="BIGINT" javaType="long"/>
@@ -53,14 +53,14 @@
values (#contentDataId#, #contentCrc#) values (#contentDataId#, #contentCrc#)
</sql> </sql>
<sql id="insert.AuditSession.AutoIncrement"> <sql id="insert.AuditApplication.AutoIncrement">
insert into alf_audit_session (audit_model_id, app_name_id) insert into alf_audit_app (audit_model_id, app_name_id)
values (#auditModelId#, #applicationNameId#) values (#auditModelId#, #applicationNameId#)
</sql> </sql>
<sql id="insert.AuditEntry.AutoIncrement"> <sql id="insert.AuditEntry.AutoIncrement">
insert into alf_audit_entry (audit_session_id, audit_user_id, audit_time, audit_values_id) insert into alf_audit_entry (audit_app_id, audit_user_id, audit_time, audit_values_id)
values (#auditSessionId#, #auditUserId#, #auditTime#, #auditValuesId#) values (#auditApplicationId#, #auditUserId#, #auditTime#, #auditValuesId#)
</sql> </sql>
<!-- --> <!-- -->
@@ -77,4 +77,14 @@
content_crc = #contentCrc# content_crc = #contentCrc#
</select> </select>
<!-- Get the audit application by model ID -->
<select id="select.AuditApplicationByModelId" parameterMap="parameter.IdMap" resultMap="result.AuditApplication">
select
*
from
alf_audit_app
where
audit_model_id = ?
</select>
</sqlMap> </sqlMap>

View File

@@ -13,8 +13,8 @@
</selectKey> </selectKey>
</insert> </insert>
<insert id="insert.AuditSession" parameterClass="AuditSession" > <insert id="insert.AuditApplication" parameterClass="AuditApplication" >
<include refid="insert.AuditSession.AutoIncrement"/> <include refid="insert.AuditApplication.AutoIncrement"/>
<selectKey resultClass="long" keyProperty="id" type="post"> <selectKey resultClass="long" keyProperty="id" type="post">
KEY_COLUMN:GENERATED_KEY KEY_COLUMN:GENERATED_KEY
</selectKey> </selectKey>

View File

@@ -31,7 +31,6 @@ import junit.framework.TestCase;
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.generator.DataGenerator.DataGeneratorScope;
import org.alfresco.repo.audit.model.AuditApplication; import org.alfresco.repo.audit.model.AuditApplication;
import org.alfresco.repo.audit.model.AuditModelException; import org.alfresco.repo.audit.model.AuditModelException;
import org.alfresco.repo.audit.model.AuditModelRegistry; import org.alfresco.repo.audit.model.AuditModelRegistry;
@@ -110,7 +109,7 @@ public class AuditBootstrapTest extends TestCase
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-04.xml"); loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-04.xml");
} }
public void testModelLoading_InvalidScope() throws Exception public void testModelLoading_InvalidDataGeneratorName() throws Exception
{ {
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-05.xml"); loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-05.xml");
} }
@@ -120,10 +119,10 @@ public class AuditBootstrapTest extends TestCase
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-06.xml"); loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-06.xml");
} }
public void testGetModelId() public void testGetApplicationId()
{ {
Long repoId = auditModelRegistry.getAuditModelId(APPLICATION_TEST); Long appId = auditModelRegistry.getAuditApplicationId(APPLICATION_TEST);
assertNotNull("No audit model ID for " + APPLICATION_TEST, repoId); assertNotNull("No audit application ID for " + APPLICATION_TEST, appId);
} }
private void testBadPath(AuditApplication app, String path) private void testBadPath(AuditApplication app, String path)
@@ -162,32 +161,32 @@ public class AuditBootstrapTest extends TestCase
assertTrue("Expected no extractors", extractors.isEmpty()); assertTrue("Expected no extractors", extractors.isEmpty());
extractors = app.getDataExtractors("/test/1.1/2.1/3.1/4.1"); extractors = app.getDataExtractors("/test/1.1/2.1/3.1/4.1");
assertEquals(2, extractors.size()); assertEquals(1, 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")); assertTrue(extractors.containsKey("/test/1.1/2.1/3.1/4.1/value.1"));
extractors = app.getDataExtractors("/test/1.1/2.1/3.1");
assertEquals(1, extractors.size());
assertTrue(extractors.containsKey("/test/1.1/2.1/3.1/value.1"));
} }
public void testAuditApplication_GetDataGenerators_AnyScope() public void testAuditApplication_GetDataGenerators()
{ {
AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST); AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST);
assertNotNull(app); assertNotNull(app);
Map<String, DataGenerator> generators = app.getDataGenerators("/blah", DataGeneratorScope.ALL); Map<String, DataGenerator> generators = app.getDataGenerators("/blah");
assertNotNull("Should never get a null map", generators); assertNotNull("Should never get a null map", generators);
assertTrue("Expected no generators", generators.isEmpty()); assertTrue("Expected no generators", generators.isEmpty());
generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1", DataGeneratorScope.ALL); generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1");
assertEquals(1, generators.size()); assertEquals(1, generators.size());
assertTrue(generators.containsKey("/test/time")); assertTrue(generators.containsKey("/test/time"));
generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1", DataGeneratorScope.SESSION); generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1");
assertEquals(1, generators.size()); assertEquals(1, generators.size());
assertTrue(generators.containsKey("/test/time")); assertTrue(generators.containsKey("/test/time"));
generators = app.getDataGenerators("/test/1.1/2.1/3.1/4.1", DataGeneratorScope.AUDIT); generators = app.getDataGenerators("/test/1.1/2.2/3.2/4.1");
assertEquals(0, generators.size());
generators = app.getDataGenerators("/test/1.1/2.2/3.2/4.1", DataGeneratorScope.ALL);
assertEquals(2, generators.size()); assertEquals(2, generators.size());
assertTrue(generators.containsKey("/test/time")); assertTrue(generators.containsKey("/test/time"));
assertTrue(generators.containsKey("/test/1.1/2.2/3.2/4.1/time")); assertTrue(generators.containsKey("/test/1.1/2.2/3.2/4.1/time"));

View File

@@ -83,38 +83,6 @@ public interface 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.
*/ */
/**
* Start an audit session for the given root path. All later audit values must start with
* the same root path.
* <p/>
* The name of the application controls part of the audit model will be used. The root path must
* start with the path separator '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR}) and the matching
* <b>key</b> attribute that was declared for the <b>Application</b> element in the audit configuration.
* <p/>
* This is a read-write method. Client code must wrap calls in the appropriate transactional wrappers.
*
* @param applicationName the name of the application to log against
* @param rootPath a base path of {@link AuditPath} key entries concatenated with
* {@link AuditApplication#AUDIT_PATH_SEPARATOR}.
* @return Returns the unique session or <tt>null</tt> if no session was created
* @throws IllegalStateException if there is not a writable transaction present
*/
AuditSession startAuditSession(String applicationName, String rootPath);
/**
* {@inheritDoc AuditComponent#startAuditSession(String, String)}
* @param applicationName the name of the application to log against
* @param rootPath a base path of {@link AuditPath} key entries concatenated with the path separator
* '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR})
* @param values values to associate with the session. These values will override or
* complement generated session-specific values
* @param rootPath a base path of {@link AuditPath} key entries concatenated with
* {@link AuditApplication#AUDIT_PATH_SEPARATOR}.
* @throws IllegalStateException if there is not a writable transaction present
*/
AuditSession startAuditSession(String applicationName, String rootPath, Map<String, Serializable> values);
/** /**
* Record a set of values against the given session. The map is a path - starting with '/' * Record a set of values against the given session. The map is a path - starting with '/'
* ({@link AuditApplication#AUDIT_PATH_SEPARATOR}), relative to the root path given when * ({@link AuditApplication#AUDIT_PATH_SEPARATOR}), relative to the root path given when
@@ -126,9 +94,11 @@ public interface AuditComponent
* <p/> * <p/>
* This is a read-write method. Client code must wrap calls in the appropriate transactional wrappers. * This is a read-write method. Client code must wrap calls in the appropriate transactional wrappers.
* *
* @param session a pre-existing audit session to continue with * @param applicationName the name of the application to log against
* @param values the values to audit mapped by {@link AuditPath} key relative to the session * @param rootPath a base path of {@link AuditPath} key entries concatenated with the path separator
* root path * '/' ({@link AuditApplication#AUDIT_PATH_SEPARATOR})
* @param values the values to audit mapped by {@link AuditPath} key relative to root path
* (may be <tt>null</tt>)
* @return Returns the values that were actually persisted, keyed by their full path. * @return Returns the values that were actually persisted, keyed by their full path.
* @throws IllegalStateException if there is not a writable transaction present * @throws IllegalStateException if there is not a writable transaction present
* *
@@ -136,5 +106,5 @@ public interface AuditComponent
* *
* @since 3.2 * @since 3.2
*/ */
Map<String, Serializable> audit(AuditSession session, Map<String, Serializable> values); Map<String, Serializable> audit(String applicationName, String rootPath, Map<String, Serializable> values);
} }

View File

@@ -28,6 +28,7 @@ import java.io.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -35,7 +36,6 @@ import java.util.Map;
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.generator.DataGenerator.DataGeneratorScope;
import org.alfresco.repo.audit.model.AuditApplication; 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;
@@ -767,27 +767,25 @@ public class AuditComponentImpl implements AuditComponent
this.auditModelRegistry = auditModelRegistry; this.auditModelRegistry = auditModelRegistry;
} }
/**
* @see #startAuditSession(String, String, Map)
* @since 3.2
*/
public AuditSession startAuditSession(String applicationName, String rootPath)
{
return startAuditSession(applicationName, rootPath, new HashMap<String, Serializable>(11));
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* @since 3.2 * @since 3.2
*/ */
public AuditSession startAuditSession(String applicationName, String rootPath, Map<String, Serializable> values) public Map<String, Serializable> audit(String applicationName, String rootPath, Map<String, Serializable> values)
{ {
ParameterCheck.mandatory("applicationName", applicationName); ParameterCheck.mandatory("applicationName", applicationName);
ParameterCheck.mandatory("values", values); ParameterCheck.mandatory("rootPath", rootPath);
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
{ {
throw new IllegalStateException("Auditing requires a read-write transaction."); throw new IllegalStateException("Auditing requires a read-write transaction.");
} }
if (values == null)
{
values = Collections.emptyMap();
}
// Get the application // Get the application
AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); AuditApplication application = auditModelRegistry.getAuditApplication(applicationName);
if (application == null) if (application == null)
@@ -796,61 +794,19 @@ public class AuditComponentImpl implements AuditComponent
{ {
logger.debug("No audit application named '" + applicationName + "' has been registered."); logger.debug("No audit application named '" + applicationName + "' has been registered.");
} }
return null; return Collections.emptyMap();
} }
// Check the path against the application // Check the path against the application
application.checkPath(rootPath); application.checkPath(rootPath);
// Get the model ID for the application // Get the model ID for the application
Long modelId = auditModelRegistry.getAuditModelId(applicationName); Long applicationId = auditModelRegistry.getAuditApplicationId(applicationName);
if (modelId == null) if (applicationId == null)
{ {
throw new AuditException("No model exists for audit application: " + applicationName); throw new AuditException("No persisted instance exists for audit application: " + applicationName);
} }
// Now create the session // TODO: Check if the root path is enabled or not
Long sessionId = auditDAO.createAuditSession(modelId, applicationName);
AuditSession session = new AuditSession(application, rootPath, sessionId);
// Generate session data
Map<String, DataGenerator> generators = application.getDataGenerators(rootPath, DataGeneratorScope.SESSION);
Map<String, Serializable> sessionData = generateData(generators);
// Extract data from the values passed in
Map<String, Serializable> extractedData = extractData(application, values);
// Combine the values
Map<String, Serializable> allData = new HashMap<String, Serializable>(sessionData);
allData.putAll(extractedData);
// Audit it
audit(session, allData);
// Done
if (logger.isDebugEnabled())
{
logger.debug("New audit session: " + session);
}
return session;
}
/**
* {@inheritDoc}
* @since 3.2
*/
public Map<String, Serializable> audit(AuditSession session, Map<String, Serializable> values)
{
ParameterCheck.mandatory("session", session);
ParameterCheck.mandatory("values", values);
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
{
throw new IllegalStateException("Auditing requires a read-write transaction.");
}
AuditApplication app = session.getApplication();
String rootPath = session.getRootPath();
Long sessionId = session.getSessionId();
// Build the key paths using the session root path // Build the key paths using the session root path
Map<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2); Map<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
for (Map.Entry<String, Serializable> entry : values.entrySet()) for (Map.Entry<String, Serializable> entry : values.entrySet())
@@ -860,27 +816,38 @@ public class AuditComponentImpl implements AuditComponent
pathedValues.put(path, entry.getValue()); pathedValues.put(path, entry.getValue());
} }
// Generate data
Map<String, DataGenerator> generators = application.getDataGenerators(pathedValues.keySet());
Map<String, Serializable> auditData = generateData(generators);
// Now extract values // Now extract values
Map<String, Serializable> extractedValues = extractData(app, pathedValues); Map<String, Serializable> extractedData = extractData(application, pathedValues);
// Combine extracted and generated values (extracted data takes precedence)
auditData.putAll(extractedData);
// Time and username are intrinsic // Time and username are intrinsic
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
String username = AuthenticationUtil.getFullyAuthenticatedUser(); String username = AuthenticationUtil.getFullyAuthenticatedUser();
// Persist the values Long entryId = null;
Long entryId = auditDAO.createAuditEntry(sessionId, time, username, pathedValues); if (!auditData.isEmpty())
{
// Persist the values
entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData);
}
// Done // Done
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug( logger.debug(
"New audit entry: \n" + "New audit entry: \n" +
" Session ID: " + sessionId + "\n" + " Applicatoin ID: " + applicationId + "\n" +
" Entry ID: " + entryId + "\n" + " Entry ID: " + entryId + "\n" +
" Path Values: " + pathedValues + "\n" + " Path Values: " + pathedValues + "\n" +
" Extracted Values: " + extractedValues); " Audit Data: " + auditData);
} }
return extractedValues; return auditData;
} }
/** /**

View File

@@ -110,11 +110,11 @@ public class AuditComponentTest extends TestCase
// Just here to fail if the basic startup fails // Just here to fail if the basic startup fails
} }
public void testStartSessionWithBadPath() throws Exception public void testAuditWithBadPath() throws Exception
{ {
try try
{ {
auditComponent.startAuditSession(APPLICATION_TEST, "test"); auditComponent.audit(APPLICATION_TEST, "/test", null);
fail("Should fail due to lack of a transaction."); fail("Should fail due to lack of a transaction.");
} }
catch (IllegalStateException e) catch (IllegalStateException e)
@@ -127,7 +127,7 @@ public class AuditComponentTest extends TestCase
{ {
try try
{ {
auditComponent.startAuditSession(APPLICATION_TEST, "test"); auditComponent.audit(APPLICATION_TEST, "test", null);
fail("Failed to detect illegal path"); fail("Failed to detect illegal path");
} }
catch (AuditModelException e) catch (AuditModelException e)
@@ -136,18 +136,16 @@ public class AuditComponentTest extends TestCase
} }
try try
{ {
auditComponent.startAuditSession(APPLICATION_TEST, "/test/"); auditComponent.audit(APPLICATION_TEST, "/test/", null);
fail("Failed to detect illegal path"); fail("Failed to detect illegal path");
} }
catch (AuditModelException e) catch (AuditModelException e)
{ {
// Expected // Expected
} }
AuditSession session; Map<String, Serializable> auditedValues = auditComponent.audit("Bogus App", "/test", null);
session = auditComponent.startAuditSession("Bogus App", "/test"); assertNotNull(auditedValues);
assertNull("Invalid app should return null session.", session); assertTrue("Invalid application should not audit anything", auditedValues.isEmpty());
session = auditComponent.startAuditSession(APPLICATION_TEST, "/test");
assertNotNull("Valid app and root path failed to create session.", session);
return null; return null;
} }
@@ -158,20 +156,18 @@ public class AuditComponentTest extends TestCase
/** /**
* Start a session and use it within a single txn * Start a session and use it within a single txn
*/ */
public void testSession_Basic() throws Exception public void testAudit_Basic() throws Exception
{ {
final RetryingTransactionCallback<Void> testCallback = new RetryingTransactionCallback<Void>() final RetryingTransactionCallback<Void> testCallback = new RetryingTransactionCallback<Void>()
{ {
public Void execute() throws Throwable public Void execute() throws Throwable
{ {
AuditSession session = auditComponent.startAuditSession(APPLICATION_TEST, "/test/1.1");
Map<String, Serializable> values = new HashMap<String, Serializable>(13); Map<String, Serializable> values = new HashMap<String, Serializable>(13);
values.put("/test/1.1/2.1/3.1/4.1", new Long(41)); values.put("/test/1.1/2.1/3.1/4.1", new Long(41));
values.put("/test/1.1/2.1/3.1/4.2", "42"); values.put("/test/1.1/2.1/3.1/4.2", "42");
values.put("/test/1.1/2.1/3.1/4.2", new Date()); values.put("/test/1.1/2.1/3.1/4.2", new Date());
auditComponent.audit(session, values); auditComponent.audit(APPLICATION_TEST, "/test/1.1", values);
return null; return null;
} }
@@ -198,7 +194,7 @@ public class AuditComponentTest extends TestCase
for (Map.Entry<String, Serializable> entry : parameters.entrySet()) for (Map.Entry<String, Serializable> entry : parameters.entrySet())
{ {
String paramName = entry.getKey(); String paramName = entry.getKey();
String path = AuditApplication.buildPath("params", paramName); String path = AuditApplication.buildPath(action, "params", paramName);
adjustedValues.put(path, entry.getValue()); adjustedValues.put(path, entry.getValue());
} }
@@ -207,10 +203,9 @@ public class AuditComponentTest extends TestCase
{ {
public Map<String, Serializable> execute() throws Throwable public Map<String, Serializable> execute() throws Throwable
{ {
String actionPath = AuditApplication.buildPath("actions-test/actions", action); String actionPath = AuditApplication.buildPath("actions-test/actions");
AuditSession session = auditComponent.startAuditSession(APPLICATION_ACTIONS_TEST, actionPath);
return auditComponent.audit(session, adjustedValues); return auditComponent.audit(APPLICATION_ACTIONS_TEST, actionPath, adjustedValues);
} }
}; };
return transactionService.getRetryingTransactionHelper().doInTransaction(auditCallback); return transactionService.getRetryingTransactionHelper().doInTransaction(auditCallback);
@@ -279,7 +274,8 @@ public class AuditComponentTest extends TestCase
Map<String, Serializable> result = auditTestAction("action-01", nodeRef, parameters); Map<String, Serializable> result = auditTestAction("action-01", nodeRef, parameters);
Map<String, Serializable> expected = new HashMap<String, Serializable>(); Map<String, Serializable> expected = new HashMap<String, Serializable>();
expected.put("/actions-test/actions/action-01/context-node/noderef", nodeRef); expected.put("/actions-test/actions/user", AuthenticationUtil.getFullyAuthenticatedUser());
expected.put("/actions-test/actions/context-node/noderef", nodeRef);
expected.put("/actions-test/actions/action-01/params/A/value", valueA); expected.put("/actions-test/actions/action-01/params/A/value", valueA);
expected.put("/actions-test/actions/action-01/params/B/value", valueB); expected.put("/actions-test/actions/action-01/params/B/value", valueB);
expected.put("/actions-test/actions/action-01/params/C/value", valueC); expected.put("/actions-test/actions/action-01/params/C/value", valueC);

View File

@@ -0,0 +1,93 @@
/*
* 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;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import org.alfresco.util.ParameterCheck;
/**
* Bean to hold audit entry data. An audit entry represents a single audit call, but the
* data stored may be a large map.
*
* @author Derek Hulley
* @since 3.2
*/
public class AuditEntry
{
private final String user;
private final long time;
private final Long valuesId;
private final Map<String, Serializable> values;
/**
* TODO: Comment
*/
public AuditEntry(String user, long time, Long valuesId, Map<String, Serializable> values)
{
ParameterCheck.mandatoryString("user", user);
ParameterCheck.mandatory("time", time);
this.user = user;
this.time = time;
this.valuesId = valuesId;
this.values = values;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(512);
sb.append("AuditEntry")
.append("[ user=").append(user)
.append(", time=").append(new Date(time))
.append(", valuesId=").append(valuesId)
.append(", values=").append(values)
.append("]");
return sb.toString();
}
public String getUser()
{
return user;
}
public long getTime()
{
return time;
}
public Long getValuesId()
{
return valuesId;
}
public Map<String, Serializable> getValues()
{
return values;
}
}

View File

@@ -122,4 +122,16 @@ public class AuditServiceImpl implements AuditService
tx.commit(); tx.commit();
} }
/*
* V3.2 from here on. Put all fixes to the older audit code before this point, please.
*/
/**
* {@inheritDoc}
*/
public void auditQuery(AuditQueryCallback callback, String auditPath, String user, long from, long to, int limit)
{
throw new UnsupportedOperationException();
}
} }

View File

@@ -1,109 +0,0 @@
/*
* 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;
import org.alfresco.repo.audit.model.AuditApplication;
import org.alfresco.util.ParameterCheck;
/**
* Bean to hold session information for repeated use.
*
* @author Derek Hulley
* @since 3.2
*/
public class AuditSession
{
private final AuditApplication application;
private final String rootPath;
private final Long sessionId;
/**
* @param application the audit application config being used
* @param rootPath the root path being used for the session
* @param sessionId the ID produced for the persisted session
*/
public AuditSession(AuditApplication application, String rootPath, Long sessionId)
{
ParameterCheck.mandatory("application", application);
ParameterCheck.mandatoryString("rootPath", rootPath);
ParameterCheck.mandatory("sessionId", sessionId);
this.application = application;
this.rootPath = rootPath;
this.sessionId = sessionId;
}
@Override
public int hashCode()
{
return (application.hashCode() + rootPath.hashCode());
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj instanceof AuditSession)
{
AuditSession that = (AuditSession) obj;
return this.application.equals(that.application) &&
this.rootPath.equals(that.rootPath);
}
else
{
return false;
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(512);
sb.append("AuditSession")
.append("[ application=").append(application)
.append(", rootPath=").append(rootPath)
.append(", sessionId=").append(sessionId)
.append("]");
return sb.toString();
}
public AuditApplication getApplication()
{
return application;
}
public String getRootPath()
{
return rootPath;
}
public Long getSessionId()
{
return sessionId;
}
}

View File

@@ -38,16 +38,6 @@ import java.io.Serializable;
*/ */
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

@@ -140,17 +140,6 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO,
this.localSessionFactory = localSessionFactory; this.localSessionFactory = localSessionFactory;
} }
/**
* Fallout implementation from new audit DAO
*
* @throws UnsupportedOperationException always
* @since 3.2
*/
public Long createAuditSession(Long modelId, String application)
{
throw new UnsupportedOperationException();
}
/** /**
* Fallout implementation from new audit DAO * Fallout implementation from new audit DAO
* *
@@ -168,7 +157,18 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO,
* @throws UnsupportedOperationException always * @throws UnsupportedOperationException always
* @since 3.2 * @since 3.2
*/ */
public Long createAuditEntry(Long sessionId, long time, String username, Map<String, Serializable> values) public Long getOrCreateAuditApplication(Long modelId, String applicationName)
{
throw new UnsupportedOperationException();
}
/**
* Fallout implementation from new audit DAO
*
* @throws UnsupportedOperationException always
* @since 3.2
*/
public Long createAuditEntry(Long applicationId, long time, String username, Map<String, Serializable> values)
{ {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@@ -27,18 +27,15 @@ package org.alfresco.repo.audit.model;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
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.generator.DataGenerator.DataGeneratorScope;
import org.alfresco.repo.audit.model._3.Application; import org.alfresco.repo.audit.model._3.Application;
import org.alfresco.repo.audit.model._3.AuditPath; import org.alfresco.repo.audit.model._3.AuditPath;
import org.alfresco.repo.audit.model._3.GenerateValue; import org.alfresco.repo.audit.model._3.GenerateValue;
import org.alfresco.repo.audit.model._3.RecordValue; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -70,8 +67,6 @@ public class AuditApplication
private Map<String, Map<String, DataExtractor>> dataExtractors = new HashMap<String, Map<String, DataExtractor>>(11); private Map<String, Map<String, DataExtractor>> dataExtractors = new HashMap<String, Map<String, DataExtractor>>(11);
/** Derived expaned map for fast lookup */ /** Derived expaned map for fast lookup */
private Map<String, Map<String, DataGenerator>> dataGenerators = new HashMap<String, Map<String, DataGenerator>>(11); 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 application the application that will be wrapped
@@ -251,43 +246,29 @@ public class AuditApplication
* Get all data generators applicable to a given path and scope. * Get all data generators applicable to a given path and scope.
* *
* @param path the audit path * @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 * @return Returns all data generators mapped to their key-path
*/ */
public Map<String, DataGenerator> getDataGenerators(String path, DataGeneratorScope scope) public Map<String, DataGenerator> getDataGenerators(String path)
{ {
Map<String, DataGenerator> generators = dataGenerators.get(path); return getDataGenerators(Collections.singleton(path));
if (generators == null) }
/**
* Get all data generators applicable to a given path and scope.
*
* @param paths the audit paths
* @return Returns all data generators mapped to their key-path
*/
public Map<String, DataGenerator> getDataGenerators(Set<String> paths)
{
Map<String, DataGenerator> amalgamatedGenerators = new HashMap<String, DataGenerator>(13);
for (String path : paths)
{ {
// Don't give back a null Map<String, DataGenerator> generators = dataGenerators.get(path);
generators = new HashMap<String, DataGenerator>(0); if (generators != null)
}
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(); // Copy values to combined map
String generatorPath = entry.getKey(); amalgamatedGenerators.putAll(generators);
DataGeneratorScope generatorScope = dataGeneratorScopes.get(generatorPath);
if (generatorScope == DataGeneratorScope.ALL)
{
// This one always applies
continue;
}
else if (generatorScope != scope)
{
// Wrong scope
iterator.remove();
}
} }
} }
@@ -296,11 +277,10 @@ public class AuditApplication
{ {
logger.debug( logger.debug(
"Looked up data generators: \n" + "Looked up data generators: \n" +
" Path: " + path + "\n" + " Paths: " + paths + "\n" +
" Scope: " + scope + "\n" + " Found: " + amalgamatedGenerators);
" Found: " + generators);
} }
return generators; return amalgamatedGenerators;
} }
/** /**
@@ -365,11 +345,11 @@ public class AuditApplication
} }
// All the extractors apply to the current path // All the extractors apply to the current path
dataExtractors.put(currentPath, upperExtractorsByPath); dataExtractors.put(currentPath, upperExtractorsByPath);
// // Data extractors only apply directly to data in which they appear. // Data extractors only apply directly to data in which they appear.
// // TODO: Examine this assumption. If it is not true, i.e. data extractors apply to // TODO: Examine this assumption. If it is not true, i.e. data extractors apply to
// // data anywhere down the hierarchy, then the followin line of code should be // data anywhere down the hierarchy, then the followin line of code should be
// // removed and the use-cases tested appropriately. // removed and the use-cases tested appropriately.
// upperExtractorsByPath.clear(); upperExtractorsByPath = new HashMap<String, DataExtractor>();
// Get the data generators declared for this key // Get the data generators declared for this key
for (GenerateValue element : auditPath.getGenerateValue()) for (GenerateValue element : auditPath.getGenerateValue())
@@ -387,26 +367,6 @@ public class AuditApplication
{ {
generateException(generatorPath, "No data generator exists for name: " + generatorName); 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 // All generators that occur earlier in the path will also be applicable here
upperGeneratorsByPath.put(generatorPath, generator); upperGeneratorsByPath.put(generatorPath, generator);
} }

View File

@@ -96,9 +96,9 @@ public class AuditModelRegistry
*/ */
private final Map<String, AuditApplication> 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 application
*/ */
private final Map<String, Long> auditModelIdsByApplicationsName; private final Map<String, Long> auditApplicationIdsByApplicationsName;
/** /**
* Default constructor * Default constructor
@@ -114,7 +114,7 @@ public class AuditModelRegistry
auditModelUrls = new HashSet<URL>(7); auditModelUrls = new HashSet<URL>(7);
auditModels = new ArrayList<Audit>(7); auditModels = new ArrayList<Audit>(7);
auditApplicationsByName = new HashMap<String, AuditApplication>(7); auditApplicationsByName = new HashMap<String, AuditApplication>(7);
auditModelIdsByApplicationsName = new HashMap<String, Long>(7); auditApplicationIdsByApplicationsName = new HashMap<String, Long>(7);
} }
/** /**
@@ -209,7 +209,7 @@ public class AuditModelRegistry
{ {
auditModels.clear(); auditModels.clear();
auditApplicationsByName.clear(); auditApplicationsByName.clear();
auditModelIdsByApplicationsName.clear(); auditApplicationIdsByApplicationsName.clear();
} }
/** /**
@@ -271,17 +271,17 @@ public class AuditModelRegistry
} }
/** /**
* Get the ID of the persisted audit model for the given application name * Get the ID of the persisted audit application for the given application name
* *
* @param applicationName the name of the audited application * @param applicationName the name of the audited application
* @return the unique ID of the persisted model (<tt>null</tt> if not found) * @return the unique ID of the persisted application (<tt>null</tt> if not found)
*/ */
public Long getAuditModelId(String applicationName) public Long getAuditApplicationId(String applicationName)
{ {
readLock.lock(); readLock.lock();
try try
{ {
return auditModelIdsByApplicationsName.get(applicationName); return auditApplicationIdsByApplicationsName.get(applicationName);
} }
finally finally
{ {
@@ -514,9 +514,13 @@ public class AuditModelRegistry
{ {
throw new AuditModelException("Audit application '" + name + "' has already been defined."); throw new AuditModelException("Audit application '" + name + "' has already been defined.");
} }
// Get the ID of the application
Long appId = auditDAO.getOrCreateAuditApplication(auditModelId, name);
AuditApplication wrapperApp = new AuditApplication(dataExtractorsByName, dataGeneratorsByName, application); AuditApplication wrapperApp = new AuditApplication(dataExtractorsByName, dataGeneratorsByName, application);
auditApplicationsByName.put(name, wrapperApp); auditApplicationsByName.put(name, wrapperApp);
auditModelIdsByApplicationsName.put(name, auditModelId); auditApplicationIdsByApplicationsName.put(name, appId);
} }
// Store the model itself // Store the model itself
auditModels.add(audit); auditModels.add(audit);

View File

@@ -17,7 +17,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;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>
@@ -33,8 +32,6 @@ 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.
@@ -60,28 +57,4 @@ 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

@@ -34,11 +34,11 @@ public class ObjectFactory {
} }
/** /**
* Create an instance of {@link KeyedAuditDefinition } * Create an instance of {@link RecordValue }
* *
*/ */
public KeyedAuditDefinition createKeyedAuditDefinition() { public RecordValue createRecordValue() {
return new KeyedAuditDefinition(); return new RecordValue();
} }
/** /**
@@ -57,14 +57,6 @@ public class ObjectFactory {
return new Audit(); return new Audit();
} }
/**
* Create an instance of {@link DataExtractors }
*
*/
public DataExtractors createDataExtractors() {
return new DataExtractors();
}
/** /**
* Create an instance of {@link AuditPath } * Create an instance of {@link AuditPath }
* *
@@ -90,11 +82,11 @@ public class ObjectFactory {
} }
/** /**
* Create an instance of {@link RecordValue } * Create an instance of {@link KeyedAuditDefinition }
* *
*/ */
public RecordValue createRecordValue() { public KeyedAuditDefinition createKeyedAuditDefinition() {
return new RecordValue(); return new KeyedAuditDefinition();
} }
/** /**
@@ -113,6 +105,14 @@ public class ObjectFactory {
return new GenerateValue(); return new GenerateValue();
} }
/**
* Create an instance of {@link DataExtractors }
*
*/
public DataExtractors createDataExtractors() {
return new DataExtractors();
}
/** /**
* Create an instance of {@link JAXBElement }{@code <}{@link Audit }{@code >}} * Create an instance of {@link JAXBElement }{@code <}{@link Audit }{@code >}}
* *

View File

@@ -1,40 +0,0 @@
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

@@ -56,7 +56,7 @@ import org.apache.commons.logging.LogFactory;
*/ */
public abstract class AbstractAuditDAOImpl implements AuditDAO public abstract class AbstractAuditDAOImpl implements AuditDAO
{ {
private static final Log logger = LogFactory.getLog(AbstractAuditDAOImpl.class); protected final Log logger = LogFactory.getLog(this.getClass());
private HibernateAuditDAO oldDAO; private HibernateAuditDAO oldDAO;
private ContentService contentService; private ContentService contentService;
@@ -83,6 +83,11 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
this.propertyValueDAO = propertyValueDAO; this.propertyValueDAO = propertyValueDAO;
} }
protected PropertyValueDAO getPropertyValueDAO()
{
return this.propertyValueDAO;
}
/* /*
* Support for older audit DAO * Support for older audit DAO
*/ */
@@ -196,34 +201,42 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
protected abstract AuditModelEntity createAuditModel(Long contentDataId, long crc); protected abstract AuditModelEntity createAuditModel(Long contentDataId, long crc);
/* /*
* alf_audit_session * alf_audit_application
*/ */
public Long createAuditSession(Long modelId, String application) public Long getOrCreateAuditApplication(Long modelId, String application)
{ {
// Persist the string // Search for it
Long appNameId = propertyValueDAO.getOrCreatePropertyValue(application).getFirst(); AuditApplicationEntity entity = getAuditApplicationByModelIdAndName(modelId, application);
// Create the audit session if (entity == null)
AuditSessionEntity entity = createAuditSession(appNameId, modelId);
// Done
if (logger.isDebugEnabled())
{ {
logger.debug( // Create it
"Created new audit session: \n" + // Persist the string
" Model: " + modelId + "\n" + Long appNameId = propertyValueDAO.getOrCreatePropertyValue(application).getFirst();
" App: " + application + "\n" + // Create the audit session
" Result: " + entity); entity = createAuditApplication(modelId, appNameId);
// Done
if (logger.isDebugEnabled())
{
logger.debug(
"Created new audit application: \n" +
" Model: " + modelId + "\n" +
" App: " + application + "\n" +
" Result: " + entity);
}
} }
// Done
return entity.getId(); return entity.getId();
} }
protected abstract AuditSessionEntity createAuditSession(Long appNameId, Long modelId); protected abstract AuditApplicationEntity getAuditApplicationByModelIdAndName(Long modelId, String appName);
protected abstract AuditApplicationEntity createAuditApplication(Long modelId, Long appNameId);
/* /*
* alf_audit_entry * alf_audit_entry
*/ */
public Long createAuditEntry(Long sessionId, long time, String username, Map<String, Serializable> values) public Long createAuditEntry(Long applicationId, long time, String username, Map<String, Serializable> values)
{ {
final Long usernameId; final Long usernameId;
if (username != null) if (username != null)
@@ -242,20 +255,20 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
} }
// Create the audit entry // Create the audit entry
AuditEntryEntity entity = createAuditEntry(sessionId, time, usernameId, valuesId); AuditEntryEntity entity = createAuditEntry(applicationId, time, usernameId, valuesId);
// Done // Done
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug( logger.debug(
"Created new audit entry: \n" + "Created new audit entry: \n" +
" Session: " + sessionId + "\n" + " Application: " + applicationId + "\n" +
" Time: " + (new Date(time)) + "\n" + " Time: " + (new Date(time)) + "\n" +
" User: " + username + "\n" + " User: " + username + "\n" +
" Result: " + entity); " Result: " + entity);
} }
return entity.getId(); return entity.getId();
} }
protected abstract AuditEntryEntity createAuditEntry(Long sessionId, long time, Long usernameId, Long valuesId); protected abstract AuditEntryEntity createAuditEntry(Long applicationId, long time, Long usernameId, Long valuesId);
} }

View File

@@ -25,18 +25,18 @@
package org.alfresco.repo.domain.audit; package org.alfresco.repo.domain.audit;
/** /**
* Entity bean for <b>alf_audit_session</b> table. * Entity bean for <b>alf_audit_application</b> table.
* *
* @author Derek Hulley * @author Derek Hulley
* @since 3.2 * @since 3.2
*/ */
public class AuditSessionEntity public class AuditApplicationEntity
{ {
private Long id; private Long id;
private Long applicationNameId;
private Long auditModelId; private Long auditModelId;
private Long applicationNameId;
public AuditSessionEntity() public AuditApplicationEntity()
{ {
} }
@@ -44,10 +44,10 @@ public class AuditSessionEntity
public String toString() public String toString()
{ {
StringBuilder sb = new StringBuilder(512); StringBuilder sb = new StringBuilder(512);
sb.append("AuditSessionEntity") sb.append("AuditApplicationEntity")
.append("[ ID=").append(id) .append("[ ID=").append(id)
.append(", applicationNameId=").append(applicationNameId)
.append(", auditModelId=").append(auditModelId) .append(", auditModelId=").append(auditModelId)
.append(", applicationNameId=").append(applicationNameId)
.append("]"); .append("]");
return sb.toString(); return sb.toString();
} }

View File

@@ -75,22 +75,22 @@ public interface AuditDAO
Pair<Long, ContentData> getOrCreateAuditModel(URL url); Pair<Long, ContentData> getOrCreateAuditModel(URL url);
/** /**
* Creates a new audit session entry - there is no session re-use. * Creates a new audit application or finds an existing one
* *
* @param modelId an existing audit model ID * @param modelId the ID of the model configuration
* @param application the name of the application * @param applicationName the name of the application
* @return Returns the unique session ID * @return Returns the ID of the application entry
*/ */
Long createAuditSession(Long modelId, String application); Long getOrCreateAuditApplication(Long modelId, String applicationName);
/** /**
* Create a new audit entry with the given map of values. * Create a new audit entry with the given map of values.
* *
* @param sessionId an existing audit session ID * @param applicationId an existing audit application ID
* @param time the time (ms since epoch) to log the entry against * @param time the time (ms since epoch) to log the entry against
* @param username the authenticated user (<tt>null</tt> if not present) * @param username the authenticated user (<tt>null</tt> if not present)
* @param values the values to record * @param values the values to record
* @return Returns the unique entry ID * @return Returns the unique entry ID
*/ */
Long createAuditEntry(Long sessionId, long time, String username, Map<String, Serializable> values); Long createAuditEntry(Long applicationId, long time, String username, Map<String, Serializable> values);
} }

View File

@@ -88,7 +88,7 @@ public class AuditDAOTest extends TestCase
assertEquals(configPair, configPairCheck); assertEquals(configPair, configPairCheck);
} }
public void testAuditSession() throws Exception public void testAuditApplicatoin() throws Exception
{ {
final File file = AbstractContentTransformerTest.loadQuickTestFile("pdf"); final File file = AbstractContentTransformerTest.loadQuickTestFile("pdf");
assertNotNull(file); assertNotNull(file);
@@ -104,22 +104,22 @@ public class AuditDAOTest extends TestCase
final String appName = getName() + "." + System.currentTimeMillis(); final String appName = getName() + "." + System.currentTimeMillis();
final int count = 1000; final int count = 1000;
RetryingTransactionCallback<Void> createSessionCallback = new RetryingTransactionCallback<Void>() RetryingTransactionCallback<Void> createAppCallback = new RetryingTransactionCallback<Void>()
{ {
public Void execute() throws Throwable public Void execute() throws Throwable
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
auditDAO.createAuditSession(modelId, appName); auditDAO.getOrCreateAuditApplication(modelId, appName);
} }
return null; return null;
} }
}; };
long before = System.nanoTime(); long before = System.nanoTime();
txnHelper.doInTransaction(createSessionCallback); txnHelper.doInTransaction(createAppCallback);
long after = System.nanoTime(); long after = System.nanoTime();
System.out.println( System.out.println(
"Time for " + count + " session creations was " + "Time for " + count + " application creations was " +
((double)(after - before)/(10E6)) + "ms"); ((double)(after - before)/(10E6)) + "ms");
} }
@@ -130,15 +130,15 @@ public class AuditDAOTest extends TestCase
final URL url = new URL("file:" + file.getAbsolutePath()); final URL url = new URL("file:" + file.getAbsolutePath());
final String appName = getName() + "." + System.currentTimeMillis(); final String appName = getName() + "." + System.currentTimeMillis();
RetryingTransactionCallback<Long> createSessionCallback = new RetryingTransactionCallback<Long>() RetryingTransactionCallback<Long> createAppCallback = new RetryingTransactionCallback<Long>()
{ {
public Long execute() throws Throwable public Long execute() throws Throwable
{ {
Long modelId = auditDAO.getOrCreateAuditModel(url).getFirst(); Long modelId = auditDAO.getOrCreateAuditModel(url).getFirst();
return auditDAO.createAuditSession(modelId, appName); return auditDAO.getOrCreateAuditApplication(modelId, appName);
} }
}; };
final Long sessionId = txnHelper.doInTransaction(createSessionCallback); final Long sessionId = txnHelper.doInTransaction(createAppCallback);
final int count = 1000; final int count = 1000;
final String username = "alexi"; final String username = "alexi";

View File

@@ -35,7 +35,7 @@ import java.util.Date;
public class AuditEntryEntity public class AuditEntryEntity
{ {
private Long id; private Long id;
private Long auditSessionId; private Long auditApplicationId;
private Long auditUserId; private Long auditUserId;
private long auditTime; private long auditTime;
private Long auditValuesId; private Long auditValuesId;
@@ -50,7 +50,7 @@ public class AuditEntryEntity
StringBuilder sb = new StringBuilder(512); StringBuilder sb = new StringBuilder(512);
sb.append("AuditEntryEntity") sb.append("AuditEntryEntity")
.append("[ ID=").append(id) .append("[ ID=").append(id)
.append(", auditSessionId=").append(auditSessionId) .append(", auditApplicationId=").append(auditApplicationId)
.append(", auditTime").append(new Date(auditTime)) .append(", auditTime").append(new Date(auditTime))
.append(", auditValuesId=").append(auditValuesId) .append(", auditValuesId=").append(auditValuesId)
.append("]"); .append("]");
@@ -67,14 +67,14 @@ public class AuditEntryEntity
this.id = id; this.id = id;
} }
public Long getAuditSessionId() public Long getAuditApplicationId()
{ {
return auditSessionId; return auditApplicationId;
} }
public void setAuditSessionId(Long auditSessionId) public void setAuditApplicationId(Long auditSessionId)
{ {
this.auditSessionId = auditSessionId; this.auditApplicationId = auditSessionId;
} }
public Long getAuditUserId() public Long getAuditUserId()

View File

@@ -24,10 +24,16 @@
*/ */
package org.alfresco.repo.domain.audit.ibatis; package org.alfresco.repo.domain.audit.ibatis;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.domain.audit.AbstractAuditDAOImpl; import org.alfresco.repo.domain.audit.AbstractAuditDAOImpl;
import org.alfresco.repo.domain.audit.AuditApplicationEntity;
import org.alfresco.repo.domain.audit.AuditEntryEntity; import org.alfresco.repo.domain.audit.AuditEntryEntity;
import org.alfresco.repo.domain.audit.AuditModelEntity; import org.alfresco.repo.domain.audit.AuditModelEntity;
import org.alfresco.repo.domain.audit.AuditSessionEntity; import org.alfresco.util.Pair;
import org.springframework.orm.ibatis.SqlMapClientTemplate; import org.springframework.orm.ibatis.SqlMapClientTemplate;
/** /**
@@ -41,7 +47,8 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl
private static final String SELECT_MODEL_BY_CRC = "select.AuditModelByCrc"; private static final String SELECT_MODEL_BY_CRC = "select.AuditModelByCrc";
private static final String INSERT_MODEL = "insert.AuditModel"; private static final String INSERT_MODEL = "insert.AuditModel";
private static final String INSERT_SESSION = "insert.AuditSession"; private static final String SELECT_APPLICATION_BY_MODEL_ID = "select.AuditApplicationByModelId";
private static final String INSERT_APPLICATION = "insert.AuditApplication";
private static final String INSERT_ENTRY = "insert.AuditEntry"; private static final String INSERT_ENTRY = "insert.AuditEntry";
@@ -75,22 +82,59 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl
return entity; return entity;
} }
@SuppressWarnings("unchecked")
@Override @Override
protected AuditSessionEntity createAuditSession(Long appNameId, Long modelId) protected AuditApplicationEntity getAuditApplicationByModelIdAndName(Long modelId, String appName)
{ {
AuditSessionEntity entity = new AuditSessionEntity(); Map<String, Object> params = new HashMap<String, Object>(11);
entity.setApplicationNameId(appNameId); params.put("id", modelId);
List<AuditApplicationEntity> results = (List<AuditApplicationEntity>) template.queryForList(
SELECT_APPLICATION_BY_MODEL_ID,
params);
// There could be multiple hits for the model ID. Go through them and find the correct app name.
AuditApplicationEntity result = null;
for (AuditApplicationEntity row : results)
{
Long appNameId = row.getApplicationNameId();
Pair<Long, Serializable> propPair = getPropertyValueDAO().getPropertyValueById(appNameId);
if (propPair == null)
{
// There is a FK to protect against this, but we'll just log it
logger.warn("An audit application references a non-existent app_name_id: " + appNameId);
}
// Check for exact match
Serializable propValue = propPair.getSecond();
if (propValue instanceof String && propValue.equals(appName))
{
// Got it
result = row;
break;
}
}
// Done
if (logger.isDebugEnabled())
{
logger.debug("Searched for audit application with model id " + modelId + " and found: " + result);
}
return result;
}
@Override
protected AuditApplicationEntity createAuditApplication(Long modelId, Long appNameId)
{
AuditApplicationEntity entity = new AuditApplicationEntity();
entity.setAuditModelId(modelId); entity.setAuditModelId(modelId);
Long id = (Long) template.insert(INSERT_SESSION, entity); entity.setApplicationNameId(appNameId);
Long id = (Long) template.insert(INSERT_APPLICATION, entity);
entity.setId(id); entity.setId(id);
return entity; return entity;
} }
@Override @Override
protected AuditEntryEntity createAuditEntry(Long sessionId, long time, Long usernameId, Long valuesId) protected AuditEntryEntity createAuditEntry(Long applicationId, long time, Long usernameId, Long valuesId)
{ {
AuditEntryEntity entity = new AuditEntryEntity(); AuditEntryEntity entity = new AuditEntryEntity();
entity.setAuditSessionId(sessionId); entity.setAuditApplicationId(applicationId);
entity.setAuditTime(time); entity.setAuditTime(time);
entity.setAuditUserId(usernameId); entity.setAuditUserId(usernameId);
entity.setAuditValuesId(valuesId); entity.setAuditValuesId(valuesId);

View File

@@ -24,7 +24,9 @@
*/ */
package org.alfresco.service.cmr.audit; package org.alfresco.service.cmr.audit;
import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map;
import org.alfresco.service.NotAuditable; import org.alfresco.service.NotAuditable;
import org.alfresco.service.PublicService; import org.alfresco.service.PublicService;
@@ -100,4 +102,62 @@ public interface AuditService
*/ */
@NotAuditable @NotAuditable
public List<AuditInfo> getAuditTrail(NodeRef nodeRef); public List<AuditInfo> getAuditTrail(NodeRef nodeRef);
/*
* V3.2 from here on. Put all fixes to the older audit code before this point, please.
*/
/**
* The interface that will be used to give query results to the calling code.
* @since 3.2
*/
public static interface AuditQueryCallback
{
/**
* Check if summary or full data fetching is required. Depending on the return value,
* the underlying query may be completely different; it is not possible to change the
* return value and expect the callback to be used differently during row handling.
*
* @return Return <tt>true</tt> if summary data is required only i.e
* the full map of audit values for the entries will not be
* retrieved.
*/
boolean isSummaryOnly();
/**
* Handle a summary row of audit entry data. The ID of the full values map is provided.
*
* @param applicationName the name of the application
* @param user the user that logged the entry
* @param time the time of the entry
* @param valuesId the ID of the values map as created
* @return Return <tt>true</tt> to continue processing rows or <tt>false</tt> to stop
*/
boolean handleAuditEntrySummary(String applicationName, String user, long time, Long valuesId);
/**
* Handle a full row of audit entry data.
*
* @param applicationName the name of the application
* @param user the user that logged the entry
* @param time the time of the entry
* @param values the values map as created
* @return Return <tt>true</tt> to continue processing rows or <tt>false</tt> to stop
*/
boolean handleAuditEntryFull(String applicationName, String user, long time, Map<String, Serializable> values);
}
/**
* Get the audit entries that match the given criteria.
*
* @param callback the callback that will handle results
* @param auditPath if not <tt>null</tt>, at least one value in the entry must start with this path
* @param user if not <tt>null</tt>, the entry must be logged against this user
* @param from the start search time (use 0L) to cover all times
* @param to the end search time (use Long.MAX_VALUE) to cover all times
* @param limit the maximum number of results to retrieve
*/
void auditQuery(
AuditQueryCallback callback,
String auditPath, String user, long from, long to, int limit);
} }

View File

@@ -11,7 +11,7 @@
<Application name="Alfresco Test Bad 02" key="test-bad-02"> <Application name="Alfresco Test Bad 02" key="test-bad-02">
<AuditPath key="1.1"> <AuditPath key="1.1">
<AuditPath key="2.1"> <AuditPath key="2.1">
<GenerateValue key="value.1" dataGenerator="blah" scope="AUDIT"/> <GenerateValue key="value.1" dataGenerator="blah"/>
</AuditPath> </AuditPath>
</AuditPath> </AuditPath>
</Application> </Application>

View File

@@ -15,7 +15,7 @@
<Application name="Alfresco Test Bad 05" key="test-bad-05"> <Application name="Alfresco Test Bad 05" key="test-bad-05">
<AuditPath key="1.1"> <AuditPath key="1.1">
<AuditPath key="2.1"> <AuditPath key="2.1">
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSIOn"/> <GenerateValue key="time" dataGenerator="systemTimeAAA"/>
</AuditPath> </AuditPath>
</AuditPath> </AuditPath>
</Application> </Application>

View File

@@ -15,7 +15,7 @@
<Application name="Alfresco Test Bad 06" key="test-bad-06"> <Application name="Alfresco Test Bad 06" key="test-bad-06">
<AuditPath key="1.1"> <AuditPath key="1.1">
<AuditPath key="2.1"> <AuditPath key="2.1">
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSION"/> <GenerateValue key="time" dataGenerator="systemTime"/>
</AuditPath> </AuditPath>
</AuditPath> </AuditPath>
</Application> </Application>

View File

@@ -20,7 +20,7 @@
</DataGenerators> </DataGenerators>
<Application name="Alfresco Test" key="test"> <Application name="Alfresco Test" key="test">
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSION"/> <GenerateValue key="time" dataGenerator="systemTime"/>
<AuditPath key="1.1"> <AuditPath key="1.1">
<AuditPath key="2.1"> <AuditPath key="2.1">
<AuditPath key="3.1"> <AuditPath key="3.1">
@@ -56,7 +56,7 @@
<AuditPath key="3.2"> <AuditPath key="3.2">
<AuditPath key="4.1"> <AuditPath key="4.1">
<RecordValue key="value.1" dataExtractor="simpleValue"/> <RecordValue key="value.1" dataExtractor="simpleValue"/>
<GenerateValue key="time" dataGenerator="systemTime" scope="ALL"/> <GenerateValue key="time" dataGenerator="systemTime"/>
</AuditPath> </AuditPath>
<AuditPath key="4.2"> <AuditPath key="4.2">
<RecordValue key="value.1" dataExtractor="simpleValue"/> <RecordValue key="value.1" dataExtractor="simpleValue"/>
@@ -67,12 +67,12 @@
</Application> </Application>
<Application name="Actions Test" key="actions-test"> <Application name="Actions Test" key="actions-test">
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSION"/>
<AuditPath key="actions"> <AuditPath key="actions">
<GenerateValue key="user" dataGenerator="user"/>
<AuditPath key="context-node">
<RecordValue key="noderef" dataExtractor="simpleValue"/>
</AuditPath>
<AuditPath key="action-01"> <AuditPath key="action-01">
<AuditPath key="context-node">
<RecordValue key="noderef" dataExtractor="simpleValue"/>
</AuditPath>
<AuditPath key="params"> <AuditPath key="params">
<AuditPath key="A"> <AuditPath key="A">
<RecordValue key="value" dataExtractor="simpleValue"/> <RecordValue key="value" dataExtractor="simpleValue"/>