mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
AuditComponent implementation and fallout
- alf_prop_string_value now includes a CRC column and handles Oracle empty string issues - All property values are/must now be Serializable for auditing - Pushing data into audit is working git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15915 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -17,14 +17,10 @@
|
||||
</DataExtractors>
|
||||
|
||||
<DataGenerators>
|
||||
<DataGenerator name="authenticatedUser" class="org.alfresco.repo.audit.generator.AuthenticatedUserDataGenerator"/>
|
||||
<DataGenerator name="systemTime" class="org.alfresco.repo.audit.generator.SystemTimeDataGenerator"/>
|
||||
<DataGenerator name="transactionId" class="org.alfresco.repo.audit.generator.TransactionIdDataGenerator"/>
|
||||
</DataGenerators>
|
||||
|
||||
<Application name="Alfresco Repository" key="repository">
|
||||
<GenerateValue key="user" dataGenerator="authenticatedUser" scope="SESSION"/>
|
||||
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSION"/>
|
||||
<AuditPath key="services">
|
||||
<GenerateValue key="txn" dataGenerator="transactionId" scope="AUDIT"/>
|
||||
<AuditPath key="nodeservice">
|
||||
@@ -49,4 +45,4 @@
|
||||
</AuditPath>
|
||||
</Application>
|
||||
|
||||
</Audit>
|
||||
</Audit>
|
||||
|
@@ -27,6 +27,20 @@ CREATE TABLE alf_audit_session
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE alf_audit_entry
|
||||
(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
audit_session_id BIGINT NOT NULL,
|
||||
audit_time BIGINT NOT NULL,
|
||||
audit_user_id BIGINT NULL,
|
||||
audit_values_id BIGINT NOT NULL,
|
||||
CONSTRAINT fk_alf_audit_ent_sess FOREIGN KEY (audit_session_id) REFERENCES alf_audit_session (id),
|
||||
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_prop FOREIGN KEY (audit_values_id) REFERENCES alf_prop_value (id),
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
--
|
||||
-- Record script finish
|
||||
--
|
||||
|
@@ -46,9 +46,10 @@ CREATE TABLE alf_prop_string_value
|
||||
(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
string_value TEXT NOT NULL,
|
||||
string_end VARCHAR(16) NOT NULL,
|
||||
INDEX idx_alf_prop_str_start (string_value(32)),
|
||||
INDEX idx_alf_prop_str_end (string_end),
|
||||
string_end_lower VARCHAR(16) NOT NULL,
|
||||
string_crc BIGINT NOT NULL,
|
||||
INDEX idx_alf_prop_str (string_value(32)),
|
||||
INDEX idx_alf_prop_crc (string_end_lower, string_crc),
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
|
@@ -12,6 +12,7 @@
|
||||
|
||||
<typeAlias alias="AuditModel" type="org.alfresco.repo.domain.audit.AuditModelEntity"/>
|
||||
<typeAlias alias="AuditSession" type="org.alfresco.repo.domain.audit.AuditSessionEntity"/>
|
||||
<typeAlias alias="AuditEntry" type="org.alfresco.repo.domain.audit.AuditEntryEntity"/>
|
||||
|
||||
<!-- -->
|
||||
<!-- Result Maps -->
|
||||
@@ -27,6 +28,13 @@
|
||||
<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"/>
|
||||
</resultMap>
|
||||
<resultMap id="result.AuditEntry" class="AuditEntry">
|
||||
<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="auditUserId" column="audit_user_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="auditTime" column="audit_time" jdbcType="BIGINT" javaType="long"/>
|
||||
<result property="auditValuesId" column="audit_values_id" jdbcType="BIGINT" javaType="long"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- -->
|
||||
<!-- Parameter Maps -->
|
||||
@@ -50,6 +58,11 @@
|
||||
values (#auditModelId#, #applicationNameId#)
|
||||
</sql>
|
||||
|
||||
<sql id="insert.AuditEntry.AutoIncrement">
|
||||
insert into alf_audit_entry (audit_session_id, audit_user_id, audit_time, audit_values_id)
|
||||
values (#auditSessionId#, #auditUserId#, #auditTime#, #auditValuesId#)
|
||||
</sql>
|
||||
|
||||
<!-- -->
|
||||
<!-- Statements -->
|
||||
<!-- -->
|
||||
|
@@ -44,7 +44,8 @@
|
||||
<resultMap id="result.PropertyStringValue" class="PropertyStringValue">
|
||||
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="stringValue" column="string_value" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
<result property="stringEnd" column="string_end" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
<result property="stringEndLower" column="string_end_lower" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
<result property="stringCrc" column="string_crc" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
</resultMap>
|
||||
<resultMap id="result.PropertyDoubleValue" class="PropertyDoubleValue">
|
||||
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
@@ -92,8 +93,8 @@
|
||||
</sql>
|
||||
|
||||
<sql id="insert.PropertyStringValue.AutoIncrement">
|
||||
insert into alf_prop_string_value (string_value, string_end)
|
||||
values (#stringValue#, #stringEnd#)
|
||||
insert into alf_prop_string_value (string_value, string_end_lower, string_crc)
|
||||
values (#stringValue#, #stringEndLower#, #stringCrc#)
|
||||
</sql>
|
||||
|
||||
<sql id="insert.PropertyDoubleValue.AutoIncrement">
|
||||
@@ -180,32 +181,16 @@
|
||||
id = #id#
|
||||
</select>
|
||||
|
||||
<!-- Get the property string value by value.
|
||||
Short strings are be pulled back from the index on the 'string_end' column
|
||||
<!-- Get the property string value by value
|
||||
-->
|
||||
<select id="select.PropertyStringValueByValue" parameterClass="PropertyStringValue" resultMap="result.PropertyStringValue">
|
||||
<select id="select.PropertyStringValueByValue" parameterClass="PropertyStringValue" resultClass="java.lang.Long">
|
||||
select
|
||||
id,
|
||||
<dynamic>
|
||||
<!-- Pulling back the string from the string_end column (if possible) more efficient -->
|
||||
<isEqual property="stringEnd" compareProperty="stringValue">
|
||||
string_end as string_value,
|
||||
string_end
|
||||
</isEqual>
|
||||
<isNotEqual property="stringEnd" compareProperty="stringValue">
|
||||
string_value,
|
||||
string_end
|
||||
</isNotEqual>
|
||||
</dynamic>
|
||||
id
|
||||
from
|
||||
alf_prop_string_value
|
||||
where
|
||||
string_end = #stringEnd#
|
||||
<dynamic>
|
||||
<isNotEqual property="stringEnd" compareProperty="stringValue">
|
||||
and string_value = #stringValue#
|
||||
</isNotEqual>
|
||||
</dynamic>
|
||||
string_end_lower = #stringEndLower# and
|
||||
string_crc = #stringCrc#
|
||||
</select>
|
||||
|
||||
<!-- Get a property double value by ID -->
|
||||
|
@@ -20,4 +20,11 @@
|
||||
</selectKey>
|
||||
</insert>
|
||||
|
||||
<insert id="insert.AuditEntry" parameterClass="AuditEntry" >
|
||||
<include refid="insert.AuditEntry.AutoIncrement"/>
|
||||
<selectKey resultClass="long" keyProperty="id" type="post">
|
||||
KEY_COLUMN:GENERATED_KEY
|
||||
</selectKey>
|
||||
</insert>
|
||||
|
||||
</sqlMap>
|
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package org.alfresco.repo.audit;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -82,29 +83,44 @@ public interface AuditComponent
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start an audit session for the given root path. All later audit operations on the resulting
|
||||
* session will be relative to this root path.
|
||||
* 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 matching <b>key</b> attribute that was declared for the matching
|
||||
* <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 application the name of the application to log against
|
||||
* @param applicationName the name of the application to log against
|
||||
* @param rootPath a base path of {@link AuditPath} key entries concatenated with <b>.</b> (period)
|
||||
* @return Returns the unique session
|
||||
* @throws IllegalStateException if there is not a writable transaction present
|
||||
*/
|
||||
public AuditSession startAuditSession(String application, String rootPath);
|
||||
AuditSession startAuditSession(String applicationName, String rootPath);
|
||||
|
||||
/**
|
||||
* {@inheritDoc #startAuditSession(String, String)}
|
||||
|
||||
* @param values values to associate with the session. These values will override or
|
||||
* complement generated session-specific values
|
||||
* @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.
|
||||
* <p/>
|
||||
* 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 values the values to audit mapped by {@link AuditPath} key relative to the session
|
||||
* root path
|
||||
* @throws IllegalStateException if there is not a writable transaction present
|
||||
*
|
||||
* @see #startAuditSession()
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public void audit(AuditSession session, Map<String, Object> values);
|
||||
void audit(AuditSession session, Map<String, Serializable> values);
|
||||
}
|
||||
|
@@ -29,9 +29,13 @@ import java.lang.reflect.Method;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.audit.extractor.DataExtractor;
|
||||
import org.alfresco.repo.audit.generator.DataGenerator;
|
||||
import org.alfresco.repo.audit.generator.DataGenerator.DataGeneratorScope;
|
||||
import org.alfresco.repo.audit.model.AuditApplication;
|
||||
import org.alfresco.repo.audit.model.AuditEntry;
|
||||
import org.alfresco.repo.audit.model.AuditModelRegistry;
|
||||
@@ -53,6 +57,7 @@ import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -751,8 +756,6 @@ public class AuditComponentImpl implements AuditComponent
|
||||
* V3.2 from here on. Put all fixes to the older audit code before this point, please.
|
||||
*/
|
||||
|
||||
private static final AuditSession NO_AUDIT_SESSION = new AuditSession();
|
||||
|
||||
private AuditModelRegistry auditModelRegistry;
|
||||
|
||||
/**
|
||||
@@ -764,8 +767,27 @@ public class AuditComponentImpl implements AuditComponent
|
||||
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}
|
||||
* @since 3.2
|
||||
*/
|
||||
public AuditSession startAuditSession(String applicationName, String rootPath, Map<String, Serializable> values)
|
||||
{
|
||||
ParameterCheck.mandatory("applicationName", applicationName);
|
||||
ParameterCheck.mandatory("values", values);
|
||||
|
||||
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
|
||||
{
|
||||
throw new IllegalStateException("Auditing requires a read-write transaction.");
|
||||
}
|
||||
// Get the application
|
||||
AuditApplication application = auditModelRegistry.getAuditApplication(applicationName);
|
||||
if (application == null)
|
||||
@@ -774,8 +796,10 @@ public class AuditComponentImpl implements AuditComponent
|
||||
{
|
||||
logger.debug("No audit application named '" + applicationName + "' has been registered.");
|
||||
}
|
||||
return NO_AUDIT_SESSION;
|
||||
return null;
|
||||
}
|
||||
// Check the path against the application
|
||||
application.checkPath(rootPath);
|
||||
// Get the model ID for the application
|
||||
Long modelId = auditModelRegistry.getAuditModelId(applicationName);
|
||||
if (modelId == null)
|
||||
@@ -783,23 +807,24 @@ public class AuditComponentImpl implements AuditComponent
|
||||
throw new AuditException("No model exists for audit application: " + applicationName);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Now create the session
|
||||
Long sessionId = auditDAO.createAuditSession(modelId, applicationName);
|
||||
// Create the session info and store it on the transaction
|
||||
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())
|
||||
{
|
||||
@@ -808,13 +833,159 @@ public class AuditComponentImpl implements AuditComponent
|
||||
return session;
|
||||
}
|
||||
|
||||
public void audit(AuditSession session, Map<String, Object> values)
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @since 3.2
|
||||
*/
|
||||
public void audit(AuditSession session, Map<String, Serializable> values)
|
||||
{
|
||||
if (session == NO_AUDIT_SESSION)
|
||||
ParameterCheck.mandatory("session", session);
|
||||
ParameterCheck.mandatory("values", values);
|
||||
|
||||
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
|
||||
{
|
||||
// For some reason, the session was not to be used
|
||||
throw new IllegalStateException("Auditing requires a read-write transaction.");
|
||||
}
|
||||
|
||||
// Audit nothing if there are no values (otherwise we're just creating maps for nothing)
|
||||
if (values.size() == 0)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Nothing audited because there are no audit values.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
Long sessionId = session.getSessionId();
|
||||
long time = System.currentTimeMillis();
|
||||
String username = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
|
||||
Long entryId = auditDAO.createAuditEntry(sessionId, time, username, values);
|
||||
|
||||
// Done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("New audit entry: " + entryId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts data from a given map using data extractors from the given application.
|
||||
*
|
||||
* @param application the application providing the data extractors
|
||||
* @param values the data values from which to generate data
|
||||
* @return Returns a map of derived data keyed by full path
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
private Map<String, Serializable> extractData(
|
||||
AuditApplication application,
|
||||
Map<String, Serializable> values)
|
||||
{
|
||||
Map<String, Serializable> newData = new HashMap<String, Serializable>(values.size() + 5);
|
||||
for (Map.Entry<String, Serializable> entry : values.entrySet())
|
||||
{
|
||||
String path = entry.getKey();
|
||||
Serializable value = entry.getValue();
|
||||
// Get the applicable extractor
|
||||
Map<String, DataExtractor> extractors = application.getDataExtractors(path);
|
||||
for (Map.Entry<String, DataExtractor> extractorElement : extractors.entrySet())
|
||||
{
|
||||
String extractorPath = extractorElement.getKey();
|
||||
DataExtractor extractor = extractorElement.getValue();
|
||||
// Check if the extraction is supported
|
||||
if (!extractor.isSupported(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Use the extractor to pull the value out
|
||||
final Serializable data;
|
||||
try
|
||||
{
|
||||
data = extractor.convert(value);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
Log extractorLogger = LogFactory.getLog(extractor.getClass());
|
||||
if (extractorLogger.isDebugEnabled())
|
||||
{
|
||||
extractorLogger.debug(
|
||||
"Failed to extract audit data: \n" +
|
||||
" Path: " + path + "\n" +
|
||||
" Raw value: " + value + "\n" +
|
||||
" Extractor: " + extractor,
|
||||
e);
|
||||
}
|
||||
else
|
||||
{
|
||||
extractorLogger.warn(
|
||||
"Failed to extract audit data (turn on DEBUG for full stack): \n" +
|
||||
" Path: " + path + "\n" +
|
||||
" Raw value: " + value + "\n" +
|
||||
" Extractor: " + extractor + "\n" +
|
||||
" Error: " + e.getMessage());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Add it to the map
|
||||
newData.put(extractorPath, data);
|
||||
}
|
||||
}
|
||||
// Done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Extracted audit data: \n" +
|
||||
" Application: " + application + "\n" +
|
||||
" Raw values: " + values + "\n" +
|
||||
" Extracted: " + newData);
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param generators the data generators
|
||||
* @return Returns a map of generated data keyed by full path
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
private Map<String, Serializable> generateData(Map<String, DataGenerator> generators)
|
||||
{
|
||||
Map<String, Serializable> newData = new HashMap<String, Serializable>(generators.size() + 5);
|
||||
for (Map.Entry<String, DataGenerator> entry : generators.entrySet())
|
||||
{
|
||||
String path = entry.getKey();
|
||||
DataGenerator generator = entry.getValue();
|
||||
final Serializable data;
|
||||
try
|
||||
{
|
||||
data = generator.getData();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
Log generatorLogger = LogFactory.getLog(generator.getClass());
|
||||
if (generatorLogger.isDebugEnabled())
|
||||
{
|
||||
generatorLogger.debug(
|
||||
"Failed to generate audit data: \n" +
|
||||
" Path: " + path + "\n" +
|
||||
" Generator: " + generator,
|
||||
e);
|
||||
}
|
||||
else
|
||||
{
|
||||
generatorLogger.warn(
|
||||
"Failed to generate audit data (turn on DEBUG for full stack): \n" +
|
||||
" Path: " + path + "\n" +
|
||||
" Generator: " + generator + "\n" +
|
||||
" Error: " + e.getMessage());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Add it to the map
|
||||
newData.put(path, data);
|
||||
}
|
||||
// Done
|
||||
return newData;
|
||||
}
|
||||
}
|
||||
|
155
source/java/org/alfresco/repo/audit/AuditComponentTest.java
Normal file
155
source/java/org/alfresco/repo/audit/AuditComponentTest.java
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.audit.model.AuditModelException;
|
||||
import org.alfresco.repo.audit.model.AuditModelRegistry;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Tests component-level auditing i.e. audit sessions and audit logging.
|
||||
*
|
||||
* @see AuditComponent
|
||||
* @see AuditComponentImpl
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 3.2
|
||||
*/
|
||||
public class AuditComponentTest extends TestCase
|
||||
{
|
||||
private static final String APPLICATION_TEST = "Alfresco Test";
|
||||
|
||||
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
private AuditModelRegistry auditModelRegistry;
|
||||
private AuditComponent auditComponent;
|
||||
private TransactionService transactionService;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
auditModelRegistry = (AuditModelRegistry) ctx.getBean("auditModel.registry");
|
||||
auditComponent = (AuditComponent) ctx.getBean("auditComponent");
|
||||
transactionService = (TransactionService) ctx.getBean("transactionService");
|
||||
|
||||
// Register the test model
|
||||
URL testModelUrl = ResourceUtils.getURL("classpath:alfresco/audit/alfresco-audit-test.xml");
|
||||
auditModelRegistry.registerModel(testModelUrl);
|
||||
auditModelRegistry.loadAuditModels();
|
||||
}
|
||||
|
||||
public void testSetUp()
|
||||
{
|
||||
// Just here to fail if the basic startup fails
|
||||
}
|
||||
|
||||
public void testStartSessionWithBadPath() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
auditComponent.startAuditSession(APPLICATION_TEST, "test");
|
||||
fail("Should fail due to lack of a transaction.");
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
RetryingTransactionCallback<Void> testCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
try
|
||||
{
|
||||
auditComponent.startAuditSession(APPLICATION_TEST, "test");
|
||||
fail("Failed to detect illegal path");
|
||||
}
|
||||
catch (AuditModelException e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
try
|
||||
{
|
||||
auditComponent.startAuditSession(APPLICATION_TEST, "/test/");
|
||||
fail("Failed to detect illegal path");
|
||||
}
|
||||
catch (AuditModelException e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
AuditSession session;
|
||||
session = auditComponent.startAuditSession("Bogus App", "/test");
|
||||
assertNull("Invalid app should return null session.", session);
|
||||
session = auditComponent.startAuditSession(APPLICATION_TEST, "/test");
|
||||
assertNotNull("Valid app and root path failed to create session.", session);
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a session and use it within a single txn
|
||||
*/
|
||||
public void testBasicSession() throws Exception
|
||||
{
|
||||
final RetryingTransactionCallback<Void> testCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
AuditSession session = auditComponent.startAuditSession(APPLICATION_TEST, "/test/1.1");
|
||||
|
||||
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.2", "42");
|
||||
|
||||
auditComponent.audit(session, values);
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
RunAsWork<Void> testRunAs = new RunAsWork<Void>()
|
||||
{
|
||||
public Void doWork() throws Exception
|
||||
{
|
||||
return transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
|
||||
}
|
||||
};
|
||||
AuthenticationUtil.runAs(testRunAs, "Peanut");
|
||||
}
|
||||
}
|
@@ -39,16 +39,6 @@ public class AuditSession
|
||||
private final String rootPath;
|
||||
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 rootPath the root path being used for the session
|
||||
|
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
package org.alfresco.repo.audit.extractor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Interface for Audit data value extractors. These are used to extract auditable values
|
||||
* from those arguments, return values, exceptions and any other value passed into the audit
|
||||
@@ -44,7 +46,7 @@ public interface DataExtractor
|
||||
* @param data the data that might be useful to this extractor (could be <tt>null</tt>)
|
||||
* @return Returns <tt>true</tt> if the data is meaningful to this extractor
|
||||
*/
|
||||
public boolean isSupported(Object data);
|
||||
public boolean isSupported(Serializable data);
|
||||
|
||||
/**
|
||||
* Convert an value passed into the audit components into a value to be recorded.
|
||||
@@ -53,5 +55,5 @@ public interface DataExtractor
|
||||
* @return the (potentially) converted value
|
||||
* @throws Throwable All errors will be handled by the calling framework
|
||||
*/
|
||||
public Object convert(Object value) throws Throwable;
|
||||
public Serializable convert(Serializable value) throws Throwable;
|
||||
}
|
||||
|
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
package org.alfresco.repo.audit.extractor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* An extractor that supports all values and does not conversion.
|
||||
* This implementation can be used as a base class, although there is little
|
||||
@@ -37,7 +39,7 @@ public class SimpleValueDataExtractor extends AbstractDataExtractor
|
||||
/**
|
||||
* @return Returns <tt>true</tt> always
|
||||
*/
|
||||
public boolean isSupported(Object data)
|
||||
public boolean isSupported(Serializable data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -45,7 +47,7 @@ public class SimpleValueDataExtractor extends AbstractDataExtractor
|
||||
/**
|
||||
* Just returns the value unchanged
|
||||
*/
|
||||
public Object convert(Object in) throws Throwable
|
||||
public Serializable convert(Serializable in) throws Throwable
|
||||
{
|
||||
return in;
|
||||
}
|
||||
|
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
package org.alfresco.repo.audit.generator;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ public class AuthenticatedUserDataGenerator extends AbstractDataGenerator
|
||||
/**
|
||||
* @return Returns the currently-authenticated user
|
||||
*/
|
||||
public Object getData() throws Throwable
|
||||
public Serializable getData() throws Throwable
|
||||
{
|
||||
return AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
}
|
||||
|
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
package org.alfresco.repo.audit.generator;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Interface for Audit data value generators.These are used to produce auditable data values
|
||||
* extract auditable values from nothing; typically these values are derived from the system
|
||||
@@ -52,5 +54,5 @@ public interface DataGenerator
|
||||
* @return Returns the generated data
|
||||
* @throws Throwable All exceptions are handled by the framework
|
||||
*/
|
||||
public Object getData() throws Throwable;
|
||||
public Serializable getData() throws Throwable;
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package org.alfresco.repo.audit.generator;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ public class SystemTimeDataGenerator extends AbstractDataGenerator
|
||||
/**
|
||||
* @return Returns the current time
|
||||
*/
|
||||
public Object getData() throws Throwable
|
||||
public Serializable getData() throws Throwable
|
||||
{
|
||||
return new Date();
|
||||
}
|
||||
|
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
package org.alfresco.repo.audit.generator;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ public class TransactionIdDataGenerator extends AbstractDataGenerator
|
||||
/**
|
||||
* @return Returns the current transaction ID (<tt>null</tt> if not in a transction)
|
||||
*/
|
||||
public Object getData() throws Throwable
|
||||
public Serializable getData() throws Throwable
|
||||
{
|
||||
return AlfrescoTransactionSupport.getTransactionId();
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@ import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.audit.AuditComponentImpl;
|
||||
@@ -161,6 +162,17 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO,
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallout implementation from new audit DAO
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
* @since 3.2
|
||||
*/
|
||||
public Long createAuditEntry(Long sessionId, long time, String username, Map<String, Serializable> values)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void audit(AuditState auditInfo)
|
||||
{
|
||||
if (auditInfo.getUserIdentifier() == null)
|
||||
|
@@ -179,6 +179,25 @@ public class AuditApplication
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a path or part of a path into a single string which always starts with the
|
||||
* {@link #AUDIT_PATH_SEPARATOR}. This can be a relative path so need not always start with
|
||||
* the application root key.
|
||||
*
|
||||
* @param pathElements the elements of the path e.g. <code>"a", "b", "c"</code>.
|
||||
* @return Returns the compiled path e.g <code>"/a/b/c"</code>.
|
||||
*/
|
||||
public String buildPath(String ... pathComponents)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(pathComponents.length * 10);
|
||||
for (String pathComponent : pathComponents)
|
||||
{
|
||||
sb.append(AUDIT_PATH_SEPARATOR).append(pathComponent);
|
||||
}
|
||||
// Done
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data extractors applicable to a given path and scope.
|
||||
*
|
||||
@@ -325,6 +344,11 @@ public class AuditApplication
|
||||
}
|
||||
// All the extractors apply to the current path
|
||||
dataExtractors.put(currentPath, upperExtractorsByPath);
|
||||
// // 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
|
||||
// // data anywhere down the hierarchy, then the followin line of code should be
|
||||
// // removed and the use-cases tested appropriately.
|
||||
// upperExtractorsByPath.clear();
|
||||
|
||||
// Get the data generators declared for this key
|
||||
for (GenerateValue element : auditPath.getGenerateValue())
|
||||
|
@@ -73,14 +73,17 @@ import org.xml.sax.SAXParseException;
|
||||
public class AuditModelRegistry
|
||||
{
|
||||
public static final String AUDIT_SCHEMA_LOCATION = "classpath:alfresco/audit/alfresco-audit-3.2.xsd";
|
||||
public static final String AUDIT_RESERVED_KEY_USERNAME = "username";
|
||||
public static final String AUDIT_RESERVED_KEY_SYSTEMTIME = "systemTime";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AuditModelRegistry.class);
|
||||
|
||||
private TransactionService transactionService;
|
||||
private AuditDAO auditDAO;
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AuditModelRegistry.class);
|
||||
|
||||
private final ReentrantReadWriteLock.ReadLock readLock;
|
||||
private final ReentrantReadWriteLock.WriteLock writeLock;
|
||||
private final ObjectFactory objectFactory;
|
||||
|
||||
private final Set<URL> auditModelUrls;
|
||||
private final List<Audit> auditModels;
|
||||
@@ -104,6 +107,8 @@ public class AuditModelRegistry
|
||||
readLock = lock.readLock();
|
||||
writeLock = lock.writeLock();
|
||||
|
||||
objectFactory = new ObjectFactory();
|
||||
|
||||
auditModelUrls = new HashSet<URL>(7);
|
||||
auditModels = new ArrayList<Audit>(7);
|
||||
dataExtractorsByName = new HashMap<String, DataExtractor>(13);
|
||||
@@ -239,15 +244,15 @@ public class AuditModelRegistry
|
||||
/**
|
||||
* Get the ID of the persisted audit model for the given application name
|
||||
*
|
||||
* @param application 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)
|
||||
*/
|
||||
public Long getAuditModelId(String application)
|
||||
public Long getAuditModelId(String applicationName)
|
||||
{
|
||||
readLock.lock();
|
||||
try
|
||||
{
|
||||
return auditModelIdsByApplicationsName.get(application);
|
||||
return auditModelIdsByApplicationsName.get(applicationName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -258,15 +263,15 @@ public class AuditModelRegistry
|
||||
/**
|
||||
* Get the application model for the given application name
|
||||
*
|
||||
* @param application the name of the audited application
|
||||
* @param applicationName the name of the audited application
|
||||
* @return the java model (<tt>null</tt> if not found)
|
||||
*/
|
||||
public AuditApplication getAuditApplication(String application)
|
||||
public AuditApplication getAuditApplication(String applicationName)
|
||||
{
|
||||
readLock.lock();
|
||||
try
|
||||
{
|
||||
return auditApplicationsByName.get(application);
|
||||
return auditApplicationsByName.get(applicationName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -364,7 +369,7 @@ public class AuditModelRegistry
|
||||
DataExtractors extractorsElement = audit.getDataExtractors();
|
||||
if (extractorsElement == null)
|
||||
{
|
||||
extractorsElement = new ObjectFactory().createDataExtractors();
|
||||
extractorsElement = objectFactory.createDataExtractors();
|
||||
}
|
||||
List<org.alfresco.repo.audit.model._3.DataExtractor> converterElements = extractorsElement.getDataExtractor();
|
||||
for (org.alfresco.repo.audit.model._3.DataExtractor converterElement : converterElements)
|
||||
@@ -404,7 +409,7 @@ public class AuditModelRegistry
|
||||
DataGenerators generatorsElement = audit.getDataGenerators();
|
||||
if (generatorsElement == null)
|
||||
{
|
||||
generatorsElement = new ObjectFactory().createDataGenerators();
|
||||
generatorsElement = objectFactory.createDataGenerators();
|
||||
}
|
||||
List<org.alfresco.repo.audit.model._3.DataGenerator> generatorElements = generatorsElement.getDataGenerator();
|
||||
for (org.alfresco.repo.audit.model._3.DataGenerator generatorElement : generatorElements)
|
||||
|
@@ -27,6 +27,7 @@ package org.alfresco.repo.domain;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import org.alfresco.repo.domain.audit.AuditDAOTest;
|
||||
import org.alfresco.repo.domain.contentdata.ContentDataDAOTest;
|
||||
import org.alfresco.repo.domain.encoding.EncodingDAOTest;
|
||||
import org.alfresco.repo.domain.hibernate.HibernateSessionHelperTest;
|
||||
@@ -53,6 +54,7 @@ public class DomainTestSuite extends TestSuite
|
||||
suite.addTestSuite(PropertyValueTest.class);
|
||||
suite.addTestSuite(QNameDAOTest.class);
|
||||
suite.addTestSuite(PropertyValueTest.class);
|
||||
suite.addTestSuite(AuditDAOTest.class);
|
||||
|
||||
return suite;
|
||||
}
|
||||
|
@@ -26,8 +26,11 @@ package org.alfresco.repo.domain.audit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
@@ -193,7 +196,7 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
|
||||
protected abstract AuditModelEntity createAuditModel(Long contentDataId, long crc);
|
||||
|
||||
/*
|
||||
* alf_audit_model
|
||||
* alf_audit_session
|
||||
*/
|
||||
|
||||
public Long createAuditSession(Long modelId, String application)
|
||||
@@ -203,8 +206,52 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
|
||||
// Create the audit session
|
||||
AuditSessionEntity entity = createAuditSession(appNameId, modelId);
|
||||
// Done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(
|
||||
"Created new audit session: \n" +
|
||||
" Model: " + modelId + "\n" +
|
||||
" App: " + application + "\n" +
|
||||
" Result: " + entity);
|
||||
}
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
protected abstract AuditSessionEntity createAuditSession(Long appNameId, Long modelId);
|
||||
|
||||
/*
|
||||
* alf_audit_entry
|
||||
*/
|
||||
|
||||
public Long createAuditEntry(Long sessionId, long time, String username, Map<String, Serializable> values)
|
||||
{
|
||||
final Long usernameId;
|
||||
if (username != null)
|
||||
{
|
||||
usernameId = propertyValueDAO.getOrCreatePropertyValue(username).getFirst();
|
||||
}
|
||||
else
|
||||
{
|
||||
usernameId = null;
|
||||
}
|
||||
// Now persist the data values
|
||||
final Long valuesId = propertyValueDAO.getOrCreatePropertyValue((Serializable)values).getFirst();
|
||||
|
||||
// Create the audit entry
|
||||
AuditEntryEntity entity = createAuditEntry(sessionId, time, usernameId, valuesId);
|
||||
|
||||
// Done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(
|
||||
"Created new audit entry: \n" +
|
||||
" Session: " + sessionId + "\n" +
|
||||
" Time: " + (new Date(time)) + "\n" +
|
||||
" User: " + username + "\n" +
|
||||
" Result: " + entity);
|
||||
}
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
protected abstract AuditEntryEntity createAuditEntry(Long sessionId, long time, Long usernameId, Long valuesId);
|
||||
}
|
||||
|
@@ -24,8 +24,10 @@
|
||||
*/
|
||||
package org.alfresco.repo.domain.audit;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.audit.AuditState;
|
||||
import org.alfresco.service.cmr.audit.AuditInfo;
|
||||
@@ -75,9 +77,20 @@ public interface AuditDAO
|
||||
/**
|
||||
* Creates a new audit session entry - there is no session re-use.
|
||||
*
|
||||
* @param modelId a pre-existing model's ID
|
||||
* @param modelId an existing audit model ID
|
||||
* @param application the name of the application
|
||||
* @return Returns the unique session ID
|
||||
*/
|
||||
Long createAuditSession(Long modelId, String application);
|
||||
|
||||
/**
|
||||
* Create a new audit entry with the given map of values.
|
||||
*
|
||||
* @param sessionId an existing audit session ID
|
||||
* @param time the time (ms since epoch) to log the entry against
|
||||
* @param username the authenticated user (<tt>null</tt> if not present)
|
||||
* @param values the values to record
|
||||
* @return Returns the unique entry ID
|
||||
*/
|
||||
Long createAuditEntry(Long sessionId, long time, String username, Map<String, Serializable> values);
|
||||
}
|
||||
|
@@ -25,7 +25,10 @@
|
||||
package org.alfresco.repo.domain.audit;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@@ -119,4 +122,44 @@ public class AuditDAOTest extends TestCase
|
||||
"Time for " + count + " session creations was " +
|
||||
((double)(after - before)/(10E6)) + "ms");
|
||||
}
|
||||
|
||||
public void testAuditEntry() throws Exception
|
||||
{
|
||||
final File file = AbstractContentTransformerTest.loadQuickTestFile("pdf");
|
||||
assertNotNull(file);
|
||||
final URL url = new URL("file:" + file.getAbsolutePath());
|
||||
final String appName = getName() + "." + System.currentTimeMillis();
|
||||
|
||||
RetryingTransactionCallback<Long> createSessionCallback = new RetryingTransactionCallback<Long>()
|
||||
{
|
||||
public Long execute() throws Throwable
|
||||
{
|
||||
Long modelId = auditDAO.getOrCreateAuditModel(url).getFirst();
|
||||
return auditDAO.createAuditSession(modelId, appName);
|
||||
}
|
||||
};
|
||||
final Long sessionId = txnHelper.doInTransaction(createSessionCallback);
|
||||
|
||||
final int count = 1000;
|
||||
final String username = "alexi";
|
||||
RetryingTransactionCallback<Void> createEntryCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Map<String, Serializable> values = Collections.singletonMap("/a/b/c", (Serializable) new Integer(i));
|
||||
long now = System.currentTimeMillis();
|
||||
auditDAO.createAuditEntry(sessionId, now, username, values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
long before = System.nanoTime();
|
||||
txnHelper.doInTransaction(createEntryCallback);
|
||||
long after = System.nanoTime();
|
||||
System.out.println(
|
||||
"Time for " + count + " entry creations was " +
|
||||
((double)(after - before)/(10E6)) + "ms");
|
||||
}
|
||||
}
|
||||
|
109
source/java/org/alfresco/repo/domain/audit/AuditEntryEntity.java
Normal file
109
source/java/org/alfresco/repo/domain/audit/AuditEntryEntity.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.domain.audit;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Entity bean for <b>alf_audit_entry</b> table.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 3.2
|
||||
*/
|
||||
public class AuditEntryEntity
|
||||
{
|
||||
private Long id;
|
||||
private Long auditSessionId;
|
||||
private Long auditUserId;
|
||||
private long auditTime;
|
||||
private Long auditValuesId;
|
||||
|
||||
public AuditEntryEntity()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(512);
|
||||
sb.append("AuditEntryEntity")
|
||||
.append("[ ID=").append(id)
|
||||
.append(", auditSessionId=").append(auditSessionId)
|
||||
.append(", auditTime").append(new Date(auditTime))
|
||||
.append(", auditValuesId=").append(auditValuesId)
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getAuditSessionId()
|
||||
{
|
||||
return auditSessionId;
|
||||
}
|
||||
|
||||
public void setAuditSessionId(Long auditSessionId)
|
||||
{
|
||||
this.auditSessionId = auditSessionId;
|
||||
}
|
||||
|
||||
public Long getAuditUserId()
|
||||
{
|
||||
return auditUserId;
|
||||
}
|
||||
|
||||
public void setAuditUserId(Long auditUserId)
|
||||
{
|
||||
this.auditUserId = auditUserId;
|
||||
}
|
||||
|
||||
public long getAuditTime()
|
||||
{
|
||||
return auditTime;
|
||||
}
|
||||
|
||||
public void setAuditTime(long auditTime)
|
||||
{
|
||||
this.auditTime = auditTime;
|
||||
}
|
||||
|
||||
public Long getAuditValuesId()
|
||||
{
|
||||
return auditValuesId;
|
||||
}
|
||||
|
||||
public void setAuditValuesId(Long auditValuesId)
|
||||
{
|
||||
this.auditValuesId = auditValuesId;
|
||||
}
|
||||
}
|
@@ -25,6 +25,7 @@
|
||||
package org.alfresco.repo.domain.audit.ibatis;
|
||||
|
||||
import org.alfresco.repo.domain.audit.AbstractAuditDAOImpl;
|
||||
import org.alfresco.repo.domain.audit.AuditEntryEntity;
|
||||
import org.alfresco.repo.domain.audit.AuditModelEntity;
|
||||
import org.alfresco.repo.domain.audit.AuditSessionEntity;
|
||||
import org.springframework.orm.ibatis.SqlMapClientTemplate;
|
||||
@@ -42,6 +43,8 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl
|
||||
|
||||
private static final String INSERT_SESSION = "insert.AuditSession";
|
||||
|
||||
private static final String INSERT_ENTRY = "insert.AuditEntry";
|
||||
|
||||
private SqlMapClientTemplate template;
|
||||
|
||||
public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate)
|
||||
@@ -82,4 +85,17 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl
|
||||
entity.setId(id);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AuditEntryEntity createAuditEntry(Long sessionId, long time, Long usernameId, Long valuesId)
|
||||
{
|
||||
AuditEntryEntity entity = new AuditEntryEntity();
|
||||
entity.setAuditSessionId(sessionId);
|
||||
entity.setAuditTime(time);
|
||||
entity.setAuditUserId(usernameId);
|
||||
entity.setAuditValuesId(valuesId);
|
||||
Long id = (Long) template.insert(INSERT_ENTRY, entity);
|
||||
entity.setId(id);
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package org.alfresco.repo.domain.propval;
|
||||
|
||||
import org.alfresco.repo.domain.CrcHelper;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
import org.alfresco.util.Pair;
|
||||
|
||||
@@ -37,7 +38,8 @@ public class PropertyStringValueEntity
|
||||
{
|
||||
private Long id;
|
||||
private String stringValue;
|
||||
private String stringEnd;
|
||||
private String stringEndLower;
|
||||
private Long stringCrc;
|
||||
|
||||
public PropertyStringValueEntity()
|
||||
{
|
||||
@@ -88,16 +90,15 @@ public class PropertyStringValueEntity
|
||||
*/
|
||||
public void setValue(String value)
|
||||
{
|
||||
this.stringValue = value;
|
||||
int len = stringValue.length();
|
||||
if (len > 16)
|
||||
if (value == null)
|
||||
{
|
||||
stringEnd = stringValue.substring(len - 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
stringEnd = stringValue;
|
||||
throw new IllegalArgumentException("Null strings cannot be persisted");
|
||||
}
|
||||
stringValue = value;
|
||||
// Calculate the crc value from the original value
|
||||
Pair<String, Long> crcPair = CrcHelper.getStringCrcPair(value, 16, false, true);
|
||||
stringEndLower = crcPair.getFirst();
|
||||
stringCrc = crcPair.getSecond();
|
||||
}
|
||||
|
||||
public Long getId()
|
||||
@@ -120,13 +121,23 @@ public class PropertyStringValueEntity
|
||||
this.stringValue = stringValue;
|
||||
}
|
||||
|
||||
public String getStringEnd()
|
||||
public String getStringEndLower()
|
||||
{
|
||||
return stringEnd;
|
||||
return stringEndLower;
|
||||
}
|
||||
|
||||
public void setStringEnd(String stringEnd)
|
||||
public void setStringEndLower(String stringEndLower)
|
||||
{
|
||||
this.stringEnd = stringEnd;
|
||||
this.stringEndLower = stringEndLower;
|
||||
}
|
||||
|
||||
public Long getStringCrc()
|
||||
{
|
||||
return stringCrc;
|
||||
}
|
||||
|
||||
public void setStringCrc(Long stringCrc)
|
||||
{
|
||||
this.stringCrc = stringCrc;
|
||||
}
|
||||
}
|
||||
|
@@ -187,7 +187,7 @@ public class PropertyValueDAOTest extends TestCase
|
||||
assertEquals(stringValue, stringEntityPair.getSecond());
|
||||
|
||||
// Check that the uppercase and lowercase strings don't have entries
|
||||
RetryingTransactionCallback<Void> getClassCallback = new RetryingTransactionCallback<Void>()
|
||||
RetryingTransactionCallback<Void> getStringCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
@@ -201,7 +201,7 @@ public class PropertyValueDAOTest extends TestCase
|
||||
return null;
|
||||
}
|
||||
};
|
||||
txnHelper.doInTransaction(getClassCallback, true);
|
||||
txnHelper.doInTransaction(getStringCallback, true);
|
||||
|
||||
RetryingTransactionCallback<Pair<Long, String>> createStringUpperCallback = new RetryingTransactionCallback<Pair<Long, String>>()
|
||||
{
|
||||
@@ -216,6 +216,22 @@ public class PropertyValueDAOTest extends TestCase
|
||||
assertNotNull(stringUpperEntityPair.getFirst());
|
||||
assertEquals(stringValueUpper, stringUpperEntityPair.getSecond());
|
||||
assertNotSame("String IDs were not different", stringEntityPair.getFirst(), stringUpperEntityPair.getFirst());
|
||||
|
||||
// Check empty string
|
||||
RetryingTransactionCallback<Void> emptyStringCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Pair<Long, String> emptyStringPair1 = propertyValueDAO.getOrCreatePropertyStringValue("");
|
||||
assertNotNull(emptyStringPair1);
|
||||
assertEquals("", emptyStringPair1.getSecond());
|
||||
Pair<Long, String> emptyStringPair2 = propertyValueDAO.getOrCreatePropertyStringValue("");
|
||||
assertNotNull(emptyStringPair2);
|
||||
assertEquals(emptyStringPair1, emptyStringPair2);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
txnHelper.doInTransaction(emptyStringCallback, true);
|
||||
}
|
||||
|
||||
public void testPropertyDoubleValue() throws Exception
|
||||
|
@@ -168,27 +168,17 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
|
||||
return value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Long findStringValueByValue(String value)
|
||||
{
|
||||
PropertyStringValueEntity entity = new PropertyStringValueEntity();
|
||||
entity.setValue(value);
|
||||
List<PropertyStringValueEntity> results = (List<PropertyStringValueEntity>) template.queryForList(
|
||||
Long id = (Long) template.queryForObject(
|
||||
SELECT_PROPERTY_STRING_VALUE_BY_VALUE,
|
||||
entity);
|
||||
// There could be several matches (if the database is case-insensitive), so find the first
|
||||
// value that matches exactly.
|
||||
for (PropertyStringValueEntity resultEntity : results)
|
||||
{
|
||||
if (value.equals(resultEntity.getStringValue()))
|
||||
{
|
||||
// Found a match
|
||||
return resultEntity.getId();
|
||||
}
|
||||
}
|
||||
// No real match
|
||||
return null;
|
||||
// The CRC match prevents incorrect results from coming back. Although there could be
|
||||
// several matches, we are sure that the matches are case-sensitive.
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -28,7 +28,9 @@
|
||||
<RecordValue key="value.1" dataExtractor="simpleValue"/>
|
||||
</AuditPath>
|
||||
<AuditPath key="4.2">
|
||||
<RecordValue key="value.1" dataExtractor="simpleValue"/>
|
||||
<RecordValue key="value.2" dataExtractor="simpleValue"/>
|
||||
<GenerateValue key="value.3" dataGenerator="systemTime" scope="AUDIT"/>
|
||||
</AuditPath>
|
||||
</AuditPath>
|
||||
<AuditPath key="3.2">
|
||||
@@ -47,7 +49,7 @@
|
||||
<RecordValue key="value.1" dataExtractor="simpleValue"/>
|
||||
</AuditPath>
|
||||
<AuditPath key="4.2">
|
||||
<RecordValue key="value.2" dataExtractor="simpleValue"/>
|
||||
<RecordValue key="value.1" dataExtractor="simpleValue"/>
|
||||
</AuditPath>
|
||||
</AuditPath>
|
||||
<AuditPath key="3.2">
|
||||
|
Reference in New Issue
Block a user