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:
@@ -36,6 +36,8 @@ import org.alfresco.repo.audit.model.AuditApplication;
|
||||
import org.alfresco.repo.audit.model.AuditModelException;
|
||||
import org.alfresco.repo.audit.model.AuditModelRegistry;
|
||||
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.util.ResourceUtils;
|
||||
|
||||
@@ -52,6 +54,7 @@ public class AuditBootstrapTest extends TestCase
|
||||
private static final String APPLICATION_TEST = "Alfresco Test";
|
||||
|
||||
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
private static final Log logger = LogFactory.getLog(AuditBootstrapTest.class);
|
||||
|
||||
private AuditModelRegistry auditModelRegistry;
|
||||
|
||||
@@ -83,6 +86,7 @@ public class AuditBootstrapTest extends TestCase
|
||||
catch (AuditModelException e)
|
||||
{
|
||||
// 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");
|
||||
}
|
||||
|
||||
public void testModelLoading_BadGeneratorRegisteredName() throws Exception
|
||||
{
|
||||
loadBadModel("classpath:alfresco/audit/alfresco-audit-test-bad-06.xml");
|
||||
}
|
||||
|
||||
public void testGetModelId()
|
||||
{
|
||||
Long repoId = auditModelRegistry.getAuditModelId(APPLICATION_TEST);
|
||||
|
@@ -109,18 +109,25 @@ public interface AuditComponent
|
||||
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/>
|
||||
* 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
|
||||
* @return Returns the values that were actually persisted, keyed by their full path.
|
||||
* @throws IllegalStateException if there is not a writable transaction present
|
||||
*
|
||||
* @see #startAuditSession()
|
||||
*
|
||||
* @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}
|
||||
* @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("values", values);
|
||||
@@ -847,27 +847,40 @@ public class AuditComponentImpl implements AuditComponent
|
||||
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)
|
||||
AuditApplication app = session.getApplication();
|
||||
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())
|
||||
{
|
||||
logger.debug("Nothing audited because there are no audit values.");
|
||||
}
|
||||
return;
|
||||
String pathElement = entry.getKey();
|
||||
String path = AuditApplication.buildPath(rootPath, pathElement);
|
||||
pathedValues.put(path, entry.getValue());
|
||||
}
|
||||
|
||||
Long sessionId = session.getSessionId();
|
||||
// Now extract values
|
||||
Map<String, Serializable> extractedValues = extractData(app, pathedValues);
|
||||
|
||||
// Time and username are intrinsic
|
||||
long time = System.currentTimeMillis();
|
||||
String username = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
|
||||
Long entryId = auditDAO.createAuditEntry(sessionId, time, username, values);
|
||||
// Persist the values
|
||||
Long entryId = auditDAO.createAuditEntry(sessionId, time, username, pathedValues);
|
||||
|
||||
// Done
|
||||
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 org.alfresco.repo.audit.model.AuditApplication;
|
||||
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.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.util.ApplicationContextHelper;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
@@ -59,22 +65,37 @@ public class AuditComponentTest extends TestCase
|
||||
|
||||
private AuditModelRegistry auditModelRegistry;
|
||||
private AuditComponent auditComponent;
|
||||
private ServiceRegistry serviceRegistry;
|
||||
private TransactionService transactionService;
|
||||
private NodeService nodeService;
|
||||
|
||||
private NodeRef nodeRef;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
auditModelRegistry = (AuditModelRegistry) ctx.getBean("auditModel.modelRegistry");
|
||||
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
|
||||
URL testModelUrl = ResourceUtils.getURL("classpath:alfresco/audit/alfresco-audit-test.xml");
|
||||
auditModelRegistry.registerModel(testModelUrl);
|
||||
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
|
||||
AuthenticationUtil.setFullyAuthenticatedUser("User-" + getName() + System.currentTimeMillis());
|
||||
AuthenticationUtil.setFullyAuthenticatedUser("User-" + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,34 +185,102 @@ public class AuditComponentTest extends TestCase
|
||||
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
|
||||
*/
|
||||
public void testSession_Extended01() throws Exception
|
||||
public void testSession_Action01() 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");
|
||||
values.put("/test/1.1/2.1/3.1/4.2", new Date());
|
||||
|
||||
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, "SomeOtherUser");
|
||||
Serializable valueA = new Date();
|
||||
Serializable valueB = "BBB-value-here";
|
||||
Serializable valueC = new Float(16.0F);
|
||||
// Get a noderef
|
||||
final Map<String, Serializable> parameters = new HashMap<String, Serializable>(13);
|
||||
parameters.put("A", valueA);
|
||||
parameters.put("B", valueB);
|
||||
parameters.put("C", valueC);
|
||||
// lowercase versions are not in the config
|
||||
parameters.put("a", valueA);
|
||||
parameters.put("b", valueB);
|
||||
parameters.put("c", valueC);
|
||||
|
||||
Map<String, Serializable> result = auditTestAction("action-01", nodeRef, parameters);
|
||||
|
||||
Map<String, Serializable> expected = new HashMap<String, Serializable>();
|
||||
expected.put("/test/actions/action-01/context-node/noderef", nodeRef);
|
||||
expected.put("/test/actions/action-01/params/A/value", valueA);
|
||||
expected.put("/test/actions/action-01/params/B/value", valueB);
|
||||
expected.put("/test/actions/action-01/params/C/value", valueC);
|
||||
|
||||
// Check
|
||||
checkAuditMaps(result, expected);
|
||||
}
|
||||
}
|
||||
|
@@ -53,6 +53,8 @@ import org.apache.commons.logging.LogFactory;
|
||||
public class AuditApplication
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -145,57 +147,73 @@ public class AuditApplication
|
||||
* Helper method to check that a path is correct for this application instance
|
||||
*
|
||||
* @param path the path in format <b>/app-key/x/y/z</b>
|
||||
* @throws AuditModelException if the path is invalid
|
||||
* @throws AuditModelException if the path is invalid
|
||||
*
|
||||
* @see #AUDIT_PATH_REGEX
|
||||
*/
|
||||
public void checkPath(String path)
|
||||
{
|
||||
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(
|
||||
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(
|
||||
path,
|
||||
"An audit path's first element must be the application's key i.e. '" + applicationKey + "'.");
|
||||
}
|
||||
if (path.endsWith(AUDIT_PATH_SEPARATOR))
|
||||
{
|
||||
generateException(
|
||||
path,
|
||||
"An audit path may not end with the separator '" + AUDIT_PATH_SEPARATOR + "'.");
|
||||
}
|
||||
if (!path.toLowerCase().equals(path))
|
||||
{
|
||||
generateException(
|
||||
path,
|
||||
"An audit path may only contain lowercase letters, '-' and '.' i.e. regex ([a-z]|[0-9]|\\-|\\.)*");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <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>.
|
||||
* @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);
|
||||
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
|
||||
return sb.toString();
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +310,7 @@ public class AuditApplication
|
||||
{
|
||||
buildAuditPaths(
|
||||
auditPath,
|
||||
"",
|
||||
null,
|
||||
new HashSet<String>(37),
|
||||
new HashMap<String, DataExtractor>(13),
|
||||
new HashMap<String, DataGenerator>(13));
|
||||
@@ -312,26 +330,29 @@ public class AuditApplication
|
||||
upperGeneratorsByPath = new HashMap<String, DataGenerator>(upperGeneratorsByPath);
|
||||
|
||||
// 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
|
||||
if (!existingPaths.add(currentPath))
|
||||
{
|
||||
generateException(currentPath, "The audit path already exists.");
|
||||
}
|
||||
// Make sure that the path is all lowercase
|
||||
checkPathCase(currentPath);
|
||||
|
||||
// Get the data extractors declared for this key
|
||||
for (RecordValue element : auditPath.getRecordValue())
|
||||
{
|
||||
String key = element.getKey();
|
||||
String extractorPath = (currentPath + AUDIT_PATH_SEPARATOR + key);
|
||||
String extractorPath = AuditApplication.buildPath(currentPath, key);
|
||||
if (!existingPaths.add(extractorPath))
|
||||
{
|
||||
generateException(extractorPath, "The audit path already exists.");
|
||||
}
|
||||
// Make sure that the path is all lowercase
|
||||
checkPathCase(extractorPath);
|
||||
|
||||
String extractorName = element.getDataExtractor();
|
||||
DataExtractor extractor = dataExtractorsByName.get(extractorName);
|
||||
@@ -354,13 +375,11 @@ public class AuditApplication
|
||||
for (GenerateValue element : auditPath.getGenerateValue())
|
||||
{
|
||||
String key = element.getKey();
|
||||
String generatorPath = (currentPath + AUDIT_PATH_SEPARATOR + key);
|
||||
String generatorPath = AuditApplication.buildPath(currentPath, key);
|
||||
if (!existingPaths.add(generatorPath))
|
||||
{
|
||||
generateException(generatorPath, "The audit path already exists.");
|
||||
}
|
||||
// Make sure that the path is all lowercase
|
||||
checkPathCase(generatorPath);
|
||||
|
||||
String generatorName = element.getDataGenerator();
|
||||
DataGenerator generator = dataGeneratorsByName.get(generatorName);
|
||||
@@ -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
|
||||
{
|
||||
throw new AuditModelException("" +
|
||||
|
@@ -435,11 +435,12 @@ public class AuditModelRegistry
|
||||
}
|
||||
else if (extractorElement.getRegisteredName() != null)
|
||||
{
|
||||
dataExtractor = dataExtractors.getNamedObject(extractorElement.getRegisteredName());
|
||||
String registeredName = extractorElement.getRegisteredName();
|
||||
dataExtractor = dataExtractors.getNamedObject(registeredName);
|
||||
if (dataExtractor == null)
|
||||
{
|
||||
throw new AuditModelException(
|
||||
"No registered audit data extractor exists for '" + name + "'.");
|
||||
"No registered audit data extractor exists for '" + registeredName + "'.");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -488,11 +489,12 @@ public class AuditModelRegistry
|
||||
}
|
||||
else if (generatorElement.getRegisteredName() != null)
|
||||
{
|
||||
dataGenerator = dataGenerators.getNamedObject(generatorElement.getRegisteredName());
|
||||
String registeredName = generatorElement.getRegisteredName();
|
||||
dataGenerator = dataGenerators.getNamedObject(registeredName);
|
||||
if (dataGenerator == null)
|
||||
{
|
||||
throw new AuditModelException(
|
||||
"No registered audit data generator exists for '" + name + "'.");
|
||||
"No registered audit data generator exists for '" + registeredName + "'.");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@@ -34,19 +34,11 @@ public class ObjectFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link Application }
|
||||
* Create an instance of {@link KeyedAuditDefinition }
|
||||
*
|
||||
*/
|
||||
public Application createApplication() {
|
||||
return new Application();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataGenerators }
|
||||
*
|
||||
*/
|
||||
public DataGenerators createDataGenerators() {
|
||||
return new DataGenerators();
|
||||
public KeyedAuditDefinition createKeyedAuditDefinition() {
|
||||
return new KeyedAuditDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,30 +49,6 @@ public class ObjectFactory {
|
||||
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 }
|
||||
*
|
||||
@@ -89,14 +57,6 @@ public class ObjectFactory {
|
||||
return new Audit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link KeyedAuditDefinition }
|
||||
*
|
||||
*/
|
||||
public KeyedAuditDefinition createKeyedAuditDefinition() {
|
||||
return new KeyedAuditDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataExtractors }
|
||||
*
|
||||
@@ -105,6 +65,14 @@ public class ObjectFactory {
|
||||
return new DataExtractors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link AuditPath }
|
||||
*
|
||||
*/
|
||||
public AuditPath createAuditPath() {
|
||||
return new AuditPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataGenerator }
|
||||
*
|
||||
@@ -113,6 +81,38 @@ public class ObjectFactory {
|
||||
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 >}}
|
||||
*
|
||||
|
@@ -235,7 +235,11 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
|
||||
usernameId = null;
|
||||
}
|
||||
// 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
|
||||
AuditEntryEntity entity = createAuditEntry(sessionId, time, usernameId, valuesId);
|
||||
|
@@ -583,7 +583,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see #getOrCreatePropertyValueImpl(Serializable, int, int)
|
||||
* @see #getOrCreatePropertyValueImpl(Serializable, Long, int, int)
|
||||
*/
|
||||
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<?, ?>)
|
||||
{
|
||||
// TODO: Go through cache?
|
||||
// The default is to do a deep expansion
|
||||
Long mapId = createPropertyMapImpl(
|
||||
(Map<? extends Serializable, ? extends Serializable>)value,
|
||||
@@ -606,11 +607,13 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
||||
maxDepth,
|
||||
currentDepth);
|
||||
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;
|
||||
}
|
||||
else if (value != null && maxDepth > currentDepth && value instanceof Collection<?>)
|
||||
{
|
||||
// TODO: Go through cache?
|
||||
// The default is to do a deep expansion
|
||||
Long collectionId = createPropertyCollectionImpl(
|
||||
(Collection<? extends Serializable>)value,
|
||||
@@ -618,7 +621,8 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
||||
maxDepth,
|
||||
currentDepth);
|
||||
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;
|
||||
}
|
||||
else
|
||||
@@ -722,6 +726,16 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
|
||||
PropertyValueEntity entity = findPropertyValueByValue(value);
|
||||
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);
|
||||
|
@@ -24,26 +24,89 @@
|
||||
*/
|
||||
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.util.ParameterCheck;
|
||||
|
||||
/**
|
||||
* Default converter for handling data going to and from the persistence layer.
|
||||
* <p/>
|
||||
* Apart from converting between <code>Boolean</code> and <code>Long</code> values,
|
||||
* the {@link DefaultTypeConverter} is used.
|
||||
* Properties are stored as a set of well-defined types defined by the enumeration
|
||||
* {@link PersistedType}. Ultimately, data can be persisted as BLOB data, but must
|
||||
* be the last resort.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 3.2
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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)
|
||||
{
|
||||
return DefaultTypeConverter.INSTANCE.convert(targetClass, value);
|
||||
|
@@ -24,6 +24,10 @@
|
||||
*/
|
||||
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.
|
||||
* <p/>
|
||||
@@ -35,6 +39,25 @@ package org.alfresco.repo.domain.propval;
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
@@ -326,7 +326,8 @@ public class PropertyValueEntity
|
||||
persistedTypeEnum = persistedTypesByClass.get(valueClazz);
|
||||
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();
|
||||
// Get the class to persist as
|
||||
@@ -345,7 +346,11 @@ public class PropertyValueEntity
|
||||
serializableValue = value;
|
||||
break;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user