mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Audit config, XSD and write-persistence tests
- Audit paths can now use mixed case (after alf_prop_string_value enhancements) - Pluggable data conversion when pushing values into persistence - Relaxed XSD to allow mixed-case key values - Regex checking of paths and names when building strings git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15976 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -104,7 +104,7 @@
|
|||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:minLength value="1"/>
|
<xs:minLength value="1"/>
|
||||||
<xs:maxLength value="128"/>
|
<xs:maxLength value="128"/>
|
||||||
<xs:pattern value="([a-z]|[0-9]|\-|\.)*"/>
|
<xs:pattern value="([a-z]|[A-Z]|[0-9]|\-|\.)*"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ CREATE TABLE alf_audit_entry
|
|||||||
audit_session_id BIGINT NOT NULL,
|
audit_session_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 NOT 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_sess FOREIGN KEY (audit_session_id) REFERENCES alf_audit_session (id),
|
||||||
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),
|
||||||
|
@@ -36,6 +36,8 @@ 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;
|
||||||
import org.alfresco.util.ApplicationContextHelper;
|
import org.alfresco.util.ApplicationContextHelper;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.util.ResourceUtils;
|
import org.springframework.util.ResourceUtils;
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ public class AuditBootstrapTest extends TestCase
|
|||||||
private static final String APPLICATION_TEST = "Alfresco Test";
|
private static final String APPLICATION_TEST = "Alfresco Test";
|
||||||
|
|
||||||
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||||
|
private static final Log logger = LogFactory.getLog(AuditBootstrapTest.class);
|
||||||
|
|
||||||
private AuditModelRegistry auditModelRegistry;
|
private AuditModelRegistry auditModelRegistry;
|
||||||
|
|
||||||
@@ -83,6 +86,7 @@ public class AuditBootstrapTest extends TestCase
|
|||||||
catch (AuditModelException e)
|
catch (AuditModelException e)
|
||||||
{
|
{
|
||||||
// Expected
|
// Expected
|
||||||
|
logger.error("Expected AuditModelException: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +115,11 @@ public class AuditBootstrapTest extends TestCase
|
|||||||
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-05.xml");
|
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-05.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testModelLoading_BadGeneratorRegisteredName() throws Exception
|
||||||
|
{
|
||||||
|
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-06.xml");
|
||||||
|
}
|
||||||
|
|
||||||
public void testGetModelId()
|
public void testGetModelId()
|
||||||
{
|
{
|
||||||
Long repoId = auditModelRegistry.getAuditModelId(APPLICATION_TEST);
|
Long repoId = auditModelRegistry.getAuditModelId(APPLICATION_TEST);
|
||||||
|
@@ -109,18 +109,25 @@ public interface AuditComponent
|
|||||||
AuditSession startAuditSession(String applicationName, String rootPath, Map<String, Serializable> values);
|
AuditSession startAuditSession(String applicationName, String rootPath, Map<String, Serializable> values);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a set of values against the given session.
|
* Record a set of values against the given session. The map is a path (starting with '/') relative
|
||||||
|
* to the root path given when {@link #startAuditSession(String, String) starting the session}. All
|
||||||
|
* resulting path values (session root path + map entry paths) must have data recorder entries and
|
||||||
|
* be enabled for data to be recorded.
|
||||||
|
* <p/>
|
||||||
|
* The return values reflect what was actually persisted and is controlled by the data extractors
|
||||||
|
* defined in the audit configuration.
|
||||||
* <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 session a pre-existing audit session to continue with
|
||||||
* @param values the values to audit mapped by {@link AuditPath} key relative to the session
|
* @param values the values to audit mapped by {@link AuditPath} key relative to the session
|
||||||
* root path
|
* root path
|
||||||
|
* @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
|
||||||
*
|
*
|
||||||
* @see #startAuditSession()
|
* @see #startAuditSession()
|
||||||
*
|
*
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
void audit(AuditSession session, Map<String, Serializable> values);
|
Map<String, Serializable> audit(AuditSession session, Map<String, Serializable> values);
|
||||||
}
|
}
|
||||||
|
@@ -837,7 +837,7 @@ public class AuditComponentImpl implements AuditComponent
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public void audit(AuditSession session, Map<String, Serializable> values)
|
public Map<String, Serializable> audit(AuditSession session, Map<String, Serializable> values)
|
||||||
{
|
{
|
||||||
ParameterCheck.mandatory("session", session);
|
ParameterCheck.mandatory("session", session);
|
||||||
ParameterCheck.mandatory("values", values);
|
ParameterCheck.mandatory("values", values);
|
||||||
@@ -847,27 +847,40 @@ public class AuditComponentImpl implements AuditComponent
|
|||||||
throw new IllegalStateException("Auditing requires a read-write transaction.");
|
throw new IllegalStateException("Auditing requires a read-write transaction.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audit nothing if there are no values (otherwise we're just creating maps for nothing)
|
AuditApplication app = session.getApplication();
|
||||||
if (values.size() == 0)
|
String rootPath = session.getRootPath();
|
||||||
|
Long sessionId = session.getSessionId();
|
||||||
|
|
||||||
|
// Build the key paths using the session root path
|
||||||
|
Map<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
|
||||||
|
for (Map.Entry<String, Serializable> entry : values.entrySet())
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
String pathElement = entry.getKey();
|
||||||
{
|
String path = AuditApplication.buildPath(rootPath, pathElement);
|
||||||
logger.debug("Nothing audited because there are no audit values.");
|
pathedValues.put(path, entry.getValue());
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Long sessionId = session.getSessionId();
|
// Now extract values
|
||||||
|
Map<String, Serializable> extractedValues = extractData(app, pathedValues);
|
||||||
|
|
||||||
|
// Time and username are intrinsic
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
String username = AuthenticationUtil.getFullyAuthenticatedUser();
|
String username = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||||
|
|
||||||
Long entryId = auditDAO.createAuditEntry(sessionId, time, username, values);
|
// Persist the values
|
||||||
|
Long entryId = auditDAO.createAuditEntry(sessionId, time, username, pathedValues);
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
logger.debug("New audit entry: " + entryId);
|
logger.debug(
|
||||||
|
"New audit entry: \n" +
|
||||||
|
" Session ID: " + sessionId + "\n" +
|
||||||
|
" Entry ID: " + entryId + "\n" +
|
||||||
|
" Path Values: " + pathedValues + "\n" +
|
||||||
|
" Extracted Values: " + extractedValues);
|
||||||
}
|
}
|
||||||
|
return extractedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -32,13 +32,19 @@ import java.util.Map;
|
|||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
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;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
|
import org.alfresco.service.ServiceRegistry;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.alfresco.util.ApplicationContextHelper;
|
import org.alfresco.util.ApplicationContextHelper;
|
||||||
|
import org.alfresco.util.EqualsHelper;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.util.ResourceUtils;
|
import org.springframework.util.ResourceUtils;
|
||||||
|
|
||||||
@@ -59,22 +65,37 @@ public class AuditComponentTest extends TestCase
|
|||||||
|
|
||||||
private AuditModelRegistry auditModelRegistry;
|
private AuditModelRegistry auditModelRegistry;
|
||||||
private AuditComponent auditComponent;
|
private AuditComponent auditComponent;
|
||||||
|
private ServiceRegistry serviceRegistry;
|
||||||
private TransactionService transactionService;
|
private TransactionService transactionService;
|
||||||
|
private NodeService nodeService;
|
||||||
|
|
||||||
|
private NodeRef nodeRef;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUp() throws Exception
|
public void setUp() throws Exception
|
||||||
{
|
{
|
||||||
auditModelRegistry = (AuditModelRegistry) ctx.getBean("auditModel.modelRegistry");
|
auditModelRegistry = (AuditModelRegistry) ctx.getBean("auditModel.modelRegistry");
|
||||||
auditComponent = (AuditComponent) ctx.getBean("auditComponent");
|
auditComponent = (AuditComponent) ctx.getBean("auditComponent");
|
||||||
transactionService = (TransactionService) ctx.getBean("transactionService");
|
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||||
|
transactionService = serviceRegistry.getTransactionService();
|
||||||
|
nodeService = serviceRegistry.getNodeService();
|
||||||
|
|
||||||
// Register the test model
|
// Register the test model
|
||||||
URL testModelUrl = ResourceUtils.getURL("classpath:alfresco/audit/alfresco-audit-test.xml");
|
URL testModelUrl = ResourceUtils.getURL("classpath:alfresco/audit/alfresco-audit-test.xml");
|
||||||
auditModelRegistry.registerModel(testModelUrl);
|
auditModelRegistry.registerModel(testModelUrl);
|
||||||
auditModelRegistry.loadAuditModels();
|
auditModelRegistry.loadAuditModels();
|
||||||
|
|
||||||
|
RunAsWork<NodeRef> testRunAs = new RunAsWork<NodeRef>()
|
||||||
|
{
|
||||||
|
public NodeRef doWork() throws Exception
|
||||||
|
{
|
||||||
|
return nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
nodeRef = AuthenticationUtil.runAs(testRunAs, AuthenticationUtil.getSystemUserName());
|
||||||
|
|
||||||
// Authenticate
|
// Authenticate
|
||||||
AuthenticationUtil.setFullyAuthenticatedUser("User-" + getName() + System.currentTimeMillis());
|
AuthenticationUtil.setFullyAuthenticatedUser("User-" + getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -164,34 +185,102 @@ public class AuditComponentTest extends TestCase
|
|||||||
AuthenticationUtil.runAs(testRunAs, "SomeOtherUser");
|
AuthenticationUtil.runAs(testRunAs, "SomeOtherUser");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Serializable> auditTestAction(
|
||||||
|
final String action,
|
||||||
|
NodeRef nodeRef,
|
||||||
|
Map<String, Serializable> parameters)
|
||||||
|
{
|
||||||
|
final Map<String, Serializable> adjustedValues = new HashMap<String, Serializable>(parameters.size() * 2);
|
||||||
|
// Add the noderef
|
||||||
|
adjustedValues.put(AuditApplication.buildPath("context-node"), nodeRef);
|
||||||
|
// Compile path-name snippets for the parameters
|
||||||
|
for (Map.Entry<String, Serializable> entry : parameters.entrySet())
|
||||||
|
{
|
||||||
|
String paramName = entry.getKey();
|
||||||
|
String path = AuditApplication.buildPath("params", paramName);
|
||||||
|
adjustedValues.put(path, entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
RetryingTransactionCallback<Map<String, Serializable>> auditCallback =
|
||||||
|
new RetryingTransactionCallback<Map<String, Serializable>>()
|
||||||
|
{
|
||||||
|
public Map<String, Serializable> execute() throws Throwable
|
||||||
|
{
|
||||||
|
String actionPath = AuditApplication.buildPath("test/actions", action);
|
||||||
|
AuditSession session = auditComponent.startAuditSession(APPLICATION_TEST, actionPath);
|
||||||
|
|
||||||
|
return auditComponent.audit(session, adjustedValues);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return transactionService.getRetryingTransactionHelper().doInTransaction(auditCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAuditMaps(Map<String, Serializable> result, Map<String, Serializable> expected)
|
||||||
|
{
|
||||||
|
Map<String, Serializable> copyResult = new HashMap<String, Serializable>(result);
|
||||||
|
|
||||||
|
boolean failure = false;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(1024);
|
||||||
|
sb.append("\nValues that don't match the expected values: ");
|
||||||
|
for (Map.Entry<String, Serializable> entry : expected.entrySet())
|
||||||
|
{
|
||||||
|
String key = entry.getKey();
|
||||||
|
Serializable expectedValue = entry.getValue();
|
||||||
|
Serializable resultValue = result.get(key);
|
||||||
|
if (!EqualsHelper.nullSafeEquals(resultValue, expectedValue))
|
||||||
|
{
|
||||||
|
sb.append("\n")
|
||||||
|
.append(" Key: ").append(key).append("\n")
|
||||||
|
.append(" Result: ").append(resultValue).append("\n")
|
||||||
|
.append(" Expected: ").append(expectedValue);
|
||||||
|
failure = true;
|
||||||
|
}
|
||||||
|
copyResult.remove(key);
|
||||||
|
}
|
||||||
|
sb.append("\nValues that are present but should not be: ");
|
||||||
|
for (Map.Entry<String, Serializable> entry : copyResult.entrySet())
|
||||||
|
{
|
||||||
|
String key = entry.getKey();
|
||||||
|
Serializable resultValue = entry.getValue();
|
||||||
|
sb.append("\n")
|
||||||
|
.append(" Key: ").append(key).append("\n")
|
||||||
|
.append(" Result: ").append(resultValue);
|
||||||
|
failure = true;
|
||||||
|
}
|
||||||
|
if (failure)
|
||||||
|
{
|
||||||
|
fail(sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a session and use it within a single txn
|
* Start a session and use it within a single txn
|
||||||
*/
|
*/
|
||||||
public void testSession_Extended01() throws Exception
|
public void testSession_Action01() throws Exception
|
||||||
{
|
{
|
||||||
final RetryingTransactionCallback<Void> testCallback = new RetryingTransactionCallback<Void>()
|
Serializable valueA = new Date();
|
||||||
{
|
Serializable valueB = "BBB-value-here";
|
||||||
public Void execute() throws Throwable
|
Serializable valueC = new Float(16.0F);
|
||||||
{
|
// Get a noderef
|
||||||
AuditSession session = auditComponent.startAuditSession(APPLICATION_TEST, "/test/1.1");
|
final Map<String, Serializable> parameters = new HashMap<String, Serializable>(13);
|
||||||
|
parameters.put("A", valueA);
|
||||||
Map<String, Serializable> values = new HashMap<String, Serializable>(13);
|
parameters.put("B", valueB);
|
||||||
values.put("/test/1.1/2.1/3.1/4.1", new Long(41));
|
parameters.put("C", valueC);
|
||||||
values.put("/test/1.1/2.1/3.1/4.2", "42");
|
// lowercase versions are not in the config
|
||||||
values.put("/test/1.1/2.1/3.1/4.2", new Date());
|
parameters.put("a", valueA);
|
||||||
|
parameters.put("b", valueB);
|
||||||
auditComponent.audit(session, values);
|
parameters.put("c", valueC);
|
||||||
|
|
||||||
return null;
|
Map<String, Serializable> result = auditTestAction("action-01", nodeRef, parameters);
|
||||||
}
|
|
||||||
};
|
Map<String, Serializable> expected = new HashMap<String, Serializable>();
|
||||||
RunAsWork<Void> testRunAs = new RunAsWork<Void>()
|
expected.put("/test/actions/action-01/context-node/noderef", nodeRef);
|
||||||
{
|
expected.put("/test/actions/action-01/params/A/value", valueA);
|
||||||
public Void doWork() throws Exception
|
expected.put("/test/actions/action-01/params/B/value", valueB);
|
||||||
{
|
expected.put("/test/actions/action-01/params/C/value", valueC);
|
||||||
return transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
|
|
||||||
}
|
// Check
|
||||||
};
|
checkAuditMaps(result, expected);
|
||||||
AuthenticationUtil.runAs(testRunAs, "SomeOtherUser");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -53,6 +53,8 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
public class AuditApplication
|
public class AuditApplication
|
||||||
{
|
{
|
||||||
public static final String AUDIT_PATH_SEPARATOR = "/";
|
public static final String AUDIT_PATH_SEPARATOR = "/";
|
||||||
|
public static final String AUDIT_KEY_REGEX = "[a-zA-Z0-9\\-\\.]+";
|
||||||
|
public static final String AUDIT_PATH_REGEX = "(/[a-zA-Z0-9\\-\\.]+)+";
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(AuditApplication.class);
|
private static final Log logger = LogFactory.getLog(AuditApplication.class);
|
||||||
|
|
||||||
@@ -145,57 +147,73 @@ public class AuditApplication
|
|||||||
* Helper method to check that a path is correct for this application instance
|
* Helper method to check that a path is correct for this application instance
|
||||||
*
|
*
|
||||||
* @param path the path in format <b>/app-key/x/y/z</b>
|
* @param path the path in format <b>/app-key/x/y/z</b>
|
||||||
* @throws AuditModelException if the path is invalid
|
* @throws AuditModelException if the path is invalid
|
||||||
|
*
|
||||||
|
* @see #AUDIT_PATH_REGEX
|
||||||
*/
|
*/
|
||||||
public void checkPath(String path)
|
public void checkPath(String path)
|
||||||
{
|
{
|
||||||
if (path == null || path.length() == 0)
|
if (path == null || path.length() == 0)
|
||||||
{
|
{
|
||||||
generateException(path, "Invalid audit path.");
|
generateException(path, "Empty or null audit path");
|
||||||
}
|
}
|
||||||
if (!path.startsWith(AUDIT_PATH_SEPARATOR))
|
else if (!path.matches(AUDIT_PATH_REGEX))
|
||||||
{
|
{
|
||||||
generateException(
|
generateException(
|
||||||
path,
|
path,
|
||||||
"An audit path must always start with the separator '" + AUDIT_PATH_SEPARATOR + "'.");
|
"An audit must match regular expression: " + AUDIT_PATH_REGEX);
|
||||||
}
|
}
|
||||||
if (path.indexOf(applicationKey, 0) != 1)
|
else if (path.indexOf(applicationKey, 0) != 1)
|
||||||
{
|
{
|
||||||
generateException(
|
generateException(
|
||||||
path,
|
path,
|
||||||
"An audit path's first element must be the application's key i.e. '" + applicationKey + "'.");
|
"An audit path's first element must be the application's key i.e. '" + applicationKey + "'.");
|
||||||
}
|
}
|
||||||
if (path.endsWith(AUDIT_PATH_SEPARATOR))
|
|
||||||
{
|
|
||||||
generateException(
|
|
||||||
path,
|
|
||||||
"An audit path may not end with the separator '" + AUDIT_PATH_SEPARATOR + "'.");
|
|
||||||
}
|
|
||||||
if (!path.toLowerCase().equals(path))
|
|
||||||
{
|
|
||||||
generateException(
|
|
||||||
path,
|
|
||||||
"An audit path may only contain lowercase letters, '-' and '.' i.e. regex ([a-z]|[0-9]|\\-|\\.)*");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile a path or part of a path into a single string which always starts with the
|
* 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
|
* {@link #AUDIT_PATH_SEPARATOR}. This can be a relative path so need not always start with
|
||||||
* the application root key.
|
* the application root key.
|
||||||
|
* <p>
|
||||||
|
* If the path separator is present at the beginning of a path component, then it is not added,
|
||||||
|
* so <code>"/a", "b", "/c"</code> becomes <code>"/a/b/c"</code> allowing path to be appended
|
||||||
|
* to other paths.
|
||||||
|
* <p>
|
||||||
|
* The final result is checked against a {@link #AUDIT_PATH_REGEX regular expression} to ensure
|
||||||
|
* it is valid.
|
||||||
*
|
*
|
||||||
* @param pathElements the elements of the path e.g. <code>"a", "b", "c"</code>.
|
* @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>.
|
* @return Returns the compiled path e.g <code>"/a/b/c"</code>.
|
||||||
*/
|
*/
|
||||||
public String buildPath(String ... pathComponents)
|
public static String buildPath(String ... pathComponents)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder(pathComponents.length * 10);
|
StringBuilder sb = new StringBuilder(pathComponents.length * 10);
|
||||||
for (String pathComponent : pathComponents)
|
for (String pathComponent : pathComponents)
|
||||||
{
|
{
|
||||||
sb.append(AUDIT_PATH_SEPARATOR).append(pathComponent);
|
if (!pathComponent.startsWith(AUDIT_PATH_SEPARATOR))
|
||||||
|
{
|
||||||
|
sb.append(AUDIT_PATH_SEPARATOR);
|
||||||
|
}
|
||||||
|
sb.append(pathComponent);
|
||||||
|
}
|
||||||
|
String path = sb.toString();
|
||||||
|
// Check the path format
|
||||||
|
if (!path.matches(AUDIT_PATH_REGEX))
|
||||||
|
{
|
||||||
|
StringBuffer msg = new StringBuffer();
|
||||||
|
msg.append("The audit path is invalid and must be matched by regular expression: ").append(AUDIT_PATH_REGEX).append("\n")
|
||||||
|
.append(" Path elements: ");
|
||||||
|
for (String pathComponent : pathComponents)
|
||||||
|
{
|
||||||
|
msg.append(pathComponent).append(", ");
|
||||||
|
}
|
||||||
|
msg.append("\n")
|
||||||
|
.append(" Result: ").append(path);
|
||||||
|
throw new AuditModelException(msg.toString());
|
||||||
}
|
}
|
||||||
// Done
|
// Done
|
||||||
return sb.toString();
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,7 +310,7 @@ public class AuditApplication
|
|||||||
{
|
{
|
||||||
buildAuditPaths(
|
buildAuditPaths(
|
||||||
auditPath,
|
auditPath,
|
||||||
"",
|
null,
|
||||||
new HashSet<String>(37),
|
new HashSet<String>(37),
|
||||||
new HashMap<String, DataExtractor>(13),
|
new HashMap<String, DataExtractor>(13),
|
||||||
new HashMap<String, DataGenerator>(13));
|
new HashMap<String, DataGenerator>(13));
|
||||||
@@ -312,26 +330,29 @@ public class AuditApplication
|
|||||||
upperGeneratorsByPath = new HashMap<String, DataGenerator>(upperGeneratorsByPath);
|
upperGeneratorsByPath = new HashMap<String, DataGenerator>(upperGeneratorsByPath);
|
||||||
|
|
||||||
// Append the current audit path to the current path
|
// Append the current audit path to the current path
|
||||||
currentPath = (currentPath + AUDIT_PATH_SEPARATOR + auditPath.getKey());
|
if (currentPath == null)
|
||||||
|
{
|
||||||
|
currentPath = AuditApplication.buildPath(auditPath.getKey());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentPath = AuditApplication.buildPath(currentPath, auditPath.getKey());
|
||||||
|
}
|
||||||
// Make sure we have not processed it before
|
// Make sure we have not processed it before
|
||||||
if (!existingPaths.add(currentPath))
|
if (!existingPaths.add(currentPath))
|
||||||
{
|
{
|
||||||
generateException(currentPath, "The audit path already exists.");
|
generateException(currentPath, "The audit path already exists.");
|
||||||
}
|
}
|
||||||
// Make sure that the path is all lowercase
|
|
||||||
checkPathCase(currentPath);
|
|
||||||
|
|
||||||
// Get the data extractors declared for this key
|
// Get the data extractors declared for this key
|
||||||
for (RecordValue element : auditPath.getRecordValue())
|
for (RecordValue element : auditPath.getRecordValue())
|
||||||
{
|
{
|
||||||
String key = element.getKey();
|
String key = element.getKey();
|
||||||
String extractorPath = (currentPath + AUDIT_PATH_SEPARATOR + key);
|
String extractorPath = AuditApplication.buildPath(currentPath, key);
|
||||||
if (!existingPaths.add(extractorPath))
|
if (!existingPaths.add(extractorPath))
|
||||||
{
|
{
|
||||||
generateException(extractorPath, "The audit path already exists.");
|
generateException(extractorPath, "The audit path already exists.");
|
||||||
}
|
}
|
||||||
// Make sure that the path is all lowercase
|
|
||||||
checkPathCase(extractorPath);
|
|
||||||
|
|
||||||
String extractorName = element.getDataExtractor();
|
String extractorName = element.getDataExtractor();
|
||||||
DataExtractor extractor = dataExtractorsByName.get(extractorName);
|
DataExtractor extractor = dataExtractorsByName.get(extractorName);
|
||||||
@@ -354,13 +375,11 @@ public class AuditApplication
|
|||||||
for (GenerateValue element : auditPath.getGenerateValue())
|
for (GenerateValue element : auditPath.getGenerateValue())
|
||||||
{
|
{
|
||||||
String key = element.getKey();
|
String key = element.getKey();
|
||||||
String generatorPath = (currentPath + AUDIT_PATH_SEPARATOR + key);
|
String generatorPath = AuditApplication.buildPath(currentPath, key);
|
||||||
if (!existingPaths.add(generatorPath))
|
if (!existingPaths.add(generatorPath))
|
||||||
{
|
{
|
||||||
generateException(generatorPath, "The audit path already exists.");
|
generateException(generatorPath, "The audit path already exists.");
|
||||||
}
|
}
|
||||||
// Make sure that the path is all lowercase
|
|
||||||
checkPathCase(generatorPath);
|
|
||||||
|
|
||||||
String generatorName = element.getDataGenerator();
|
String generatorName = element.getDataGenerator();
|
||||||
DataGenerator generator = dataGeneratorsByName.get(generatorName);
|
DataGenerator generator = dataGeneratorsByName.get(generatorName);
|
||||||
@@ -401,14 +420,6 @@ public class AuditApplication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPathCase(String path)
|
|
||||||
{
|
|
||||||
if (!path.equals(path.toLowerCase()))
|
|
||||||
{
|
|
||||||
generateException(path, "Audit key entries may only be in lowercase to ensure case-insensitivity.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateException(String path, String msg) throws AuditModelException
|
private void generateException(String path, String msg) throws AuditModelException
|
||||||
{
|
{
|
||||||
throw new AuditModelException("" +
|
throw new AuditModelException("" +
|
||||||
|
@@ -435,11 +435,12 @@ public class AuditModelRegistry
|
|||||||
}
|
}
|
||||||
else if (extractorElement.getRegisteredName() != null)
|
else if (extractorElement.getRegisteredName() != null)
|
||||||
{
|
{
|
||||||
dataExtractor = dataExtractors.getNamedObject(extractorElement.getRegisteredName());
|
String registeredName = extractorElement.getRegisteredName();
|
||||||
|
dataExtractor = dataExtractors.getNamedObject(registeredName);
|
||||||
if (dataExtractor == null)
|
if (dataExtractor == null)
|
||||||
{
|
{
|
||||||
throw new AuditModelException(
|
throw new AuditModelException(
|
||||||
"No registered audit data extractor exists for '" + name + "'.");
|
"No registered audit data extractor exists for '" + registeredName + "'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -488,11 +489,12 @@ public class AuditModelRegistry
|
|||||||
}
|
}
|
||||||
else if (generatorElement.getRegisteredName() != null)
|
else if (generatorElement.getRegisteredName() != null)
|
||||||
{
|
{
|
||||||
dataGenerator = dataGenerators.getNamedObject(generatorElement.getRegisteredName());
|
String registeredName = generatorElement.getRegisteredName();
|
||||||
|
dataGenerator = dataGenerators.getNamedObject(registeredName);
|
||||||
if (dataGenerator == null)
|
if (dataGenerator == null)
|
||||||
{
|
{
|
||||||
throw new AuditModelException(
|
throw new AuditModelException(
|
||||||
"No registered audit data generator exists for '" + name + "'.");
|
"No registered audit data generator exists for '" + registeredName + "'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -34,19 +34,11 @@ public class ObjectFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of {@link Application }
|
* Create an instance of {@link KeyedAuditDefinition }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Application createApplication() {
|
public KeyedAuditDefinition createKeyedAuditDefinition() {
|
||||||
return new Application();
|
return new KeyedAuditDefinition();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of {@link DataGenerators }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public DataGenerators createDataGenerators() {
|
|
||||||
return new DataGenerators();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,30 +49,6 @@ public class ObjectFactory {
|
|||||||
return new DataExtractor();
|
return new DataExtractor();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of {@link AuditPath }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public AuditPath createAuditPath() {
|
|
||||||
return new AuditPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of {@link RecordValue }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public RecordValue createRecordValue() {
|
|
||||||
return new RecordValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of {@link GenerateValue }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public GenerateValue createGenerateValue() {
|
|
||||||
return new GenerateValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of {@link Audit }
|
* Create an instance of {@link Audit }
|
||||||
*
|
*
|
||||||
@@ -89,14 +57,6 @@ public class ObjectFactory {
|
|||||||
return new Audit();
|
return new Audit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of {@link KeyedAuditDefinition }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public KeyedAuditDefinition createKeyedAuditDefinition() {
|
|
||||||
return new KeyedAuditDefinition();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of {@link DataExtractors }
|
* Create an instance of {@link DataExtractors }
|
||||||
*
|
*
|
||||||
@@ -105,6 +65,14 @@ public class ObjectFactory {
|
|||||||
return new DataExtractors();
|
return new DataExtractors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of {@link AuditPath }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public AuditPath createAuditPath() {
|
||||||
|
return new AuditPath();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of {@link DataGenerator }
|
* Create an instance of {@link DataGenerator }
|
||||||
*
|
*
|
||||||
@@ -113,6 +81,38 @@ public class ObjectFactory {
|
|||||||
return new DataGenerator();
|
return new DataGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of {@link DataGenerators }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public DataGenerators createDataGenerators() {
|
||||||
|
return new DataGenerators();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of {@link RecordValue }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public RecordValue createRecordValue() {
|
||||||
|
return new RecordValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of {@link Application }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Application createApplication() {
|
||||||
|
return new Application();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of {@link GenerateValue }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public GenerateValue createGenerateValue() {
|
||||||
|
return new GenerateValue();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of {@link JAXBElement }{@code <}{@link Audit }{@code >}}
|
* Create an instance of {@link JAXBElement }{@code <}{@link Audit }{@code >}}
|
||||||
*
|
*
|
||||||
|
@@ -235,7 +235,11 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
|
|||||||
usernameId = null;
|
usernameId = null;
|
||||||
}
|
}
|
||||||
// Now persist the data values
|
// Now persist the data values
|
||||||
final Long valuesId = propertyValueDAO.getOrCreatePropertyValue((Serializable)values).getFirst();
|
Long valuesId = null;
|
||||||
|
if (values != null && values.size() > 0)
|
||||||
|
{
|
||||||
|
valuesId = propertyValueDAO.getOrCreatePropertyValue((Serializable)values).getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
// Create the audit entry
|
// Create the audit entry
|
||||||
AuditEntryEntity entity = createAuditEntry(sessionId, time, usernameId, valuesId);
|
AuditEntryEntity entity = createAuditEntry(sessionId, time, usernameId, valuesId);
|
||||||
|
@@ -583,7 +583,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
* @see #getOrCreatePropertyValueImpl(Serializable, int, int)
|
* @see #getOrCreatePropertyValueImpl(Serializable, Long, int, int)
|
||||||
*/
|
*/
|
||||||
public Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value, int maxDepth)
|
public Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value, int maxDepth)
|
||||||
{
|
{
|
||||||
@@ -599,6 +599,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
|||||||
{
|
{
|
||||||
if (value != null && maxDepth > currentDepth && value instanceof Map<?, ?>)
|
if (value != null && maxDepth > currentDepth && value instanceof Map<?, ?>)
|
||||||
{
|
{
|
||||||
|
// TODO: Go through cache?
|
||||||
// The default is to do a deep expansion
|
// The default is to do a deep expansion
|
||||||
Long mapId = createPropertyMapImpl(
|
Long mapId = createPropertyMapImpl(
|
||||||
(Map<? extends Serializable, ? extends Serializable>)value,
|
(Map<? extends Serializable, ? extends Serializable>)value,
|
||||||
@@ -606,11 +607,13 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
|||||||
maxDepth,
|
maxDepth,
|
||||||
currentDepth);
|
currentDepth);
|
||||||
Pair<Long, Serializable> entityPair = new Pair<Long, Serializable>(mapId, value);
|
Pair<Long, Serializable> entityPair = new Pair<Long, Serializable>(mapId, value);
|
||||||
// TODO: Go through cache?
|
// Cache instance by ID only
|
||||||
|
propertyValueCache.updateValue(mapId, value);
|
||||||
return entityPair;
|
return entityPair;
|
||||||
}
|
}
|
||||||
else if (value != null && maxDepth > currentDepth && value instanceof Collection<?>)
|
else if (value != null && maxDepth > currentDepth && value instanceof Collection<?>)
|
||||||
{
|
{
|
||||||
|
// TODO: Go through cache?
|
||||||
// The default is to do a deep expansion
|
// The default is to do a deep expansion
|
||||||
Long collectionId = createPropertyCollectionImpl(
|
Long collectionId = createPropertyCollectionImpl(
|
||||||
(Collection<? extends Serializable>)value,
|
(Collection<? extends Serializable>)value,
|
||||||
@@ -618,7 +621,8 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
|||||||
maxDepth,
|
maxDepth,
|
||||||
currentDepth);
|
currentDepth);
|
||||||
Pair<Long, Serializable> entityPair = new Pair<Long, Serializable>(collectionId, value);
|
Pair<Long, Serializable> entityPair = new Pair<Long, Serializable>(collectionId, value);
|
||||||
// TODO: Go through cache?
|
// Cache instance by ID only
|
||||||
|
propertyValueCache.updateValue(collectionId, value);
|
||||||
return entityPair;
|
return entityPair;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -722,6 +726,16 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
|||||||
PropertyValueEntity entity = findPropertyValueByValue(value);
|
PropertyValueEntity entity = findPropertyValueByValue(value);
|
||||||
return convertEntityToPair(entity);
|
return convertEntityToPair(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-op. This is implemented as we just want to update the cache.
|
||||||
|
* @return Returns 0 always
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int updateValue(Long key, Serializable value)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract List<PropertyIdSearchRow> findPropertyValueById(Long id);
|
protected abstract List<PropertyIdSearchRow> findPropertyValueById(Long id);
|
||||||
|
@@ -24,26 +24,89 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.domain.propval;
|
package org.alfresco.repo.domain.propval;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.Period;
|
||||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||||
|
import org.alfresco.util.ParameterCheck;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default converter for handling data going to and from the persistence layer.
|
* Default converter for handling data going to and from the persistence layer.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Apart from converting between <code>Boolean</code> and <code>Long</code> values,
|
* Properties are stored as a set of well-defined types defined by the enumeration
|
||||||
* the {@link DefaultTypeConverter} is used.
|
* {@link PersistedType}. Ultimately, data can be persisted as BLOB data, but must
|
||||||
|
* be the last resort.
|
||||||
*
|
*
|
||||||
* @author Derek Hulley
|
* @author Derek Hulley
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public class DefaultPropertyTypeConverter implements PropertyTypeConverter
|
public class DefaultPropertyTypeConverter implements PropertyTypeConverter
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* An unmodifiable map of types and how they should be persisted
|
||||||
|
*/
|
||||||
|
protected static final Map<Class<?>, PersistedType> defaultPersistedTypesByClass;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
// Create the map of class-type
|
||||||
|
Map<Class<?>, PersistedType> mapClass = new HashMap<Class<?>, PersistedType>(29);
|
||||||
|
mapClass.put(NodeRef.class, PersistedType.STRING);
|
||||||
|
mapClass.put(Period.class, PersistedType.STRING);
|
||||||
|
mapClass.put(Locale.class, PersistedType.STRING);
|
||||||
|
defaultPersistedTypesByClass = Collections.unmodifiableMap(mapClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Class<?>, PersistedType> persistenceMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor
|
* Default constructor
|
||||||
*/
|
*/
|
||||||
public DefaultPropertyTypeConverter()
|
public DefaultPropertyTypeConverter()
|
||||||
{
|
{
|
||||||
|
persistenceMapping = new HashMap<Class<?>, PersistedType>(
|
||||||
|
DefaultPropertyTypeConverter.defaultPersistedTypesByClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow subclasses to add further type mappings specific to the implementation
|
||||||
|
*
|
||||||
|
* @param clazz the class to be converted
|
||||||
|
* @param targetType the target persisted type
|
||||||
|
*/
|
||||||
|
protected void addTypeMapping(Class<?> clazz, PersistedType targetType)
|
||||||
|
{
|
||||||
|
this.persistenceMapping.put(clazz, targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public PersistedType getPersistentType(Serializable value)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatory("value", value);
|
||||||
|
|
||||||
|
Class<?> clazz = value.getClass();
|
||||||
|
PersistedType type = persistenceMapping.get(clazz);
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
return PersistedType.SERIALIZABLE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the conversion
|
||||||
|
*/
|
||||||
public <T> T convert(Class<T> targetClass, Object value)
|
public <T> T convert(Class<T> targetClass, Object value)
|
||||||
{
|
{
|
||||||
return DefaultTypeConverter.INSTANCE.convert(targetClass, value);
|
return DefaultTypeConverter.INSTANCE.convert(targetClass, value);
|
||||||
|
@@ -24,6 +24,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.domain.propval;
|
package org.alfresco.repo.domain.propval;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for converters that to translate between persisted values and external values.
|
* Interface for converters that to translate between persisted values and external values.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -35,6 +39,25 @@ package org.alfresco.repo.domain.propval;
|
|||||||
*/
|
*/
|
||||||
public interface PropertyTypeConverter
|
public interface PropertyTypeConverter
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* When external to persisted type mappings are not obvious, the persistence framework,
|
||||||
|
* before persisting as {@link PersistedType#SERIALIZABLE}, will give the converter
|
||||||
|
* a chance to choose how the value must be persisted:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link PersistedType#LONG}</li>
|
||||||
|
* <li>{@link PersistedType#DOUBLE}</li>
|
||||||
|
* <li>{@link PersistedType#STRING}</li>
|
||||||
|
* <li>{@link PersistedType#SERIALIZABLE}</li>
|
||||||
|
* </ul>
|
||||||
|
* The converter should return {@link PersistedType#SERIALIZABLE} if no further conversions
|
||||||
|
* are possible. Implicit in the return value is the converter's ability to do the
|
||||||
|
* conversion when required.
|
||||||
|
*
|
||||||
|
* @param value the value that does not have an obvious persistence slot
|
||||||
|
* @return Returns the type of persistence to use
|
||||||
|
*/
|
||||||
|
PersistedType getPersistentType(Serializable value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a value to a given type.
|
* Convert a value to a given type.
|
||||||
*
|
*
|
||||||
|
@@ -326,7 +326,8 @@ public class PropertyValueEntity
|
|||||||
persistedTypeEnum = persistedTypesByClass.get(valueClazz);
|
persistedTypeEnum = persistedTypesByClass.get(valueClazz);
|
||||||
if (persistedTypeEnum == null)
|
if (persistedTypeEnum == null)
|
||||||
{
|
{
|
||||||
persistedTypeEnum = PersistedType.SERIALIZABLE;
|
// Give the converter a chance to change the type it must be persisted as
|
||||||
|
persistedTypeEnum = converter.getPersistentType(value);
|
||||||
}
|
}
|
||||||
persistedType = persistedTypeEnum.getOrdinalNumber();
|
persistedType = persistedTypeEnum.getOrdinalNumber();
|
||||||
// Get the class to persist as
|
// Get the class to persist as
|
||||||
@@ -345,7 +346,11 @@ public class PropertyValueEntity
|
|||||||
serializableValue = value;
|
serializableValue = value;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Should not be able to get through switch");
|
throw new IllegalStateException(
|
||||||
|
"PropertyTypeConverter.convertToPersistentType returned illegal type: " +
|
||||||
|
" Converter: " + converter + "\n" +
|
||||||
|
" Type Returned: " + persistedTypeEnum + "\n" +
|
||||||
|
" From Value: " + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,10 @@
|
|||||||
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
|
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<DataExtractors>
|
||||||
|
<DataExtractor name="simpleValue" registeredName="auditModel.extractor.simpleValue"/>
|
||||||
|
</DataExtractors>
|
||||||
|
|
||||||
<Application name="Alfresco Test Bad 03" key="test-bad-03">
|
<Application name="Alfresco Test Bad 03" key="test-bad-03">
|
||||||
<AuditPath key="1.1">
|
<AuditPath key="1.1">
|
||||||
<AuditPath key="2.1">
|
<AuditPath key="2.1">
|
||||||
|
@@ -8,10 +8,14 @@
|
|||||||
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
|
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<DataExtractors>
|
||||||
|
<DataExtractor name="simpleValue" registeredName="auditModel.extractor.simpleValue"/>
|
||||||
|
</DataExtractors>
|
||||||
|
|
||||||
<Application name="Alfresco Test Bad 04" key="test-bad-04">
|
<Application name="Alfresco Test Bad 04" key="test-bad-04">
|
||||||
<AuditPath key="1.1">
|
<AuditPath key="1.1">
|
||||||
<AuditPath key="2.1">
|
<AuditPath key="2.1">
|
||||||
<RecordValue key="value.UPPER" dataExtractor="simpleValue"/>
|
<RecordValue key="value.£" dataExtractor="simpleValue"/>
|
||||||
</AuditPath>
|
</AuditPath>
|
||||||
</AuditPath>
|
</AuditPath>
|
||||||
</Application>
|
</Application>
|
||||||
|
@@ -8,6 +8,10 @@
|
|||||||
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
|
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<DataGenerators>
|
||||||
|
<DataGenerator name="systemTime" registeredName="auditModel.generator.time"/>
|
||||||
|
</DataGenerators>
|
||||||
|
|
||||||
<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">
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
|
||||||
|
<!-- Default Audit Configuration -->
|
||||||
|
|
||||||
|
<Audit
|
||||||
|
xmlns="http://www.alfresco.org/repo/audit/model/3.2"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.alfresco.org/repo/audit/model/3.2 alfresco-audit-3.2.xsd"
|
||||||
|
>
|
||||||
|
|
||||||
|
<DataGenerators>
|
||||||
|
<DataGenerator name="systemTime" registeredName="blah"/>
|
||||||
|
</DataGenerators>
|
||||||
|
|
||||||
|
<Application name="Alfresco Test Bad 06" key="test-bad-06">
|
||||||
|
<AuditPath key="1.1">
|
||||||
|
<AuditPath key="2.1">
|
||||||
|
<GenerateValue key="time" dataGenerator="systemTime" scope="SESSION"/>
|
||||||
|
</AuditPath>
|
||||||
|
</AuditPath>
|
||||||
|
</Application>
|
||||||
|
|
||||||
|
</Audit>
|
@@ -64,9 +64,22 @@
|
|||||||
</AuditPath>
|
</AuditPath>
|
||||||
</AuditPath>
|
</AuditPath>
|
||||||
</AuditPath>
|
</AuditPath>
|
||||||
<AuditPath key="node">
|
<AuditPath key="actions">
|
||||||
<AuditPath key="t1">
|
<AuditPath key="action-01">
|
||||||
<RecordValue key="noderef" dataExtractor="simpleValue"/>
|
<AuditPath key="context-node">
|
||||||
|
<RecordValue key="noderef" dataExtractor="simpleValue"/>
|
||||||
|
</AuditPath>
|
||||||
|
<AuditPath key="params">
|
||||||
|
<AuditPath key="A">
|
||||||
|
<RecordValue key="value" dataExtractor="simpleValue"/>
|
||||||
|
</AuditPath>
|
||||||
|
<AuditPath key="B">
|
||||||
|
<RecordValue key="value" dataExtractor="simpleValue"/>
|
||||||
|
</AuditPath>
|
||||||
|
<AuditPath key="C">
|
||||||
|
<RecordValue key="value" dataExtractor="simpleValue"/>
|
||||||
|
</AuditPath>
|
||||||
|
</AuditPath>
|
||||||
</AuditPath>
|
</AuditPath>
|
||||||
</AuditPath>
|
</AuditPath>
|
||||||
</Application>
|
</Application>
|
||||||
|
Reference in New Issue
Block a user