mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
28236: ALF-8810: Removed trailing space from discussion.discussion_for Italian translation 28241: Incremented version revision for 3.4.4 28284: ALF-835 - WCM/AVM: copy (empty) folder into itself 28285: ALF-6863: More than one cifs device breaks the web UI (explorer) 28290: ALF-8840: user-*.atomentry.ftl 28291: ALF-6863: Continuation of fix by Arseny 28336: ALF-8768: Fixed typo in comment on wcm-bootstrap-context.xml 28363: Merged DEV to V3.4-BUG-FIX 28262: ALF-8847: WCM: OrphanReaper contention throws error after 39 retries. Checkin Comment: Use JobLockService to make sure that only one OrphanReaper job is working. Generate list of nodes that must be processed in OrphanReaper.doBatch() transaction. 28386: ALF-9100: Merged PATCHES/V3.4.1 to V3.4-BUG-FIX 28249: ALF-8946: Avoid one full table scan per batch in full reindex - Now each batch scans a single time sample, dynamically adjusted based on the number of transactions in the previous sample, always aiming for 1000 transactions per sample. 28394: Fixed ALF-9090: NPE during inter-cluster subsystem messaging - Bean ID is a List<String> and might not be recognized on receiving machine - Log warning when bean ID is not available (unsymmetrical configuration, perhaps?) 28396: Merged DEV to V3.4-BUG-FIX 28384: ALF-6150: Initial state lost when non-versionable document is saved for the first time Creation of new version of document before writing its content was added to - AbstractAlfrescoMethodHandler->putDocument (this method is used by Office 2003, 2007) - VtiIfHeaderAction->doPut (this method is used by Office 2007 and 2010 on Windows 7) Creation of new version was added twice to AbstractAlfrescoMethodHandler to avoid affecting initial version when transaction is committed. 28432: Merged DEV to V3.4-BUG-FIX 28431: ALF-8530: Pressing the info icon creates an unrecorded file in the ContentStore Use ContentService.getTempWriter() in BaseContentNode$TemplateContentData.getContentAsText() method. 28435: Merged DEV/TEMPORARY to V3.4-BUG-FIX 28428: ALF-9015: cm:modifier not updated when document is updated via CIFS In ContentDiskDriver.closeFile() added ContentModel.PROP_MODIFIER property update. 28436: ALF-8550: Number of http requests (currentThreadsBusy) increases when session times out during creation of webform - Corrected use of read and write locks 28465: Fix for ALF-8023 Share preview doesn't work if... fixed as outlined by Dmitry. 28478: Merged BRANCHES/DEV/ALAN/AUDIT to BRANCHES/DEV/V3.4-BUG-FIX: 28062-28477 (28062,28063,28080,28081,28302,28303,28334,28340,28464,28469,28477) ALF-8438 Need higher level audit of user actions git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28481 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
505 lines
17 KiB
Java
505 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.audit.model;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.alfresco.repo.audit.extractor.DataExtractor;
|
|
import org.alfresco.repo.audit.generator.DataGenerator;
|
|
import org.alfresco.repo.audit.model._3.Application;
|
|
import org.alfresco.repo.audit.model._3.AuditPath;
|
|
import org.alfresco.repo.audit.model._3.GenerateValue;
|
|
import org.alfresco.repo.audit.model._3.RecordValue;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
|
/**
|
|
* Helper class that wraps the {@link Application audit application}.
|
|
* Once wrapped, client code doesn't need access to any of the generated
|
|
* model-driven classes.
|
|
*
|
|
* @author Derek Hulley
|
|
* @since 3.2
|
|
*/
|
|
public class AuditApplication
|
|
{
|
|
public static final String AUDIT_PATH_SEPARATOR = "/";
|
|
public static final String AUDIT_KEY_REGEX = "[a-zA-Z0-9\\-\\_\\.]+";
|
|
public static final String AUDIT_PATH_REGEX = "(/[a-zA-Z0-9:\\-\\_\\.]+)+";
|
|
public static final String AUDIT_INVALID_PATH_COMP_CHAR_REGEX = "[^a-zA-Z0-9:\\-\\_\\.]";
|
|
|
|
private static final Log logger = LogFactory.getLog(AuditApplication.class);
|
|
|
|
private final String applicationName;
|
|
private final String applicationKey;
|
|
|
|
private final Map<String, DataExtractor> dataExtractorsByName;
|
|
private final Map<String, DataGenerator> dataGeneratorsByName;
|
|
@SuppressWarnings("unused")
|
|
private final Application application;
|
|
private final Long applicationId;
|
|
private final Long disabledPathsId;
|
|
|
|
/** Derived expaned map for fast lookup */
|
|
private List<DataExtractorDefinition> dataExtractors = new ArrayList<DataExtractorDefinition>();
|
|
/** Derived expaned map for fast lookup */
|
|
private Map<String, Map<String, DataGenerator>> dataGenerators = new HashMap<String, Map<String, DataGenerator>>(11);
|
|
|
|
/**
|
|
* @param application the application that will be wrapped
|
|
* @param dataExtractorsByName data extractors to use
|
|
* @param dataGeneratorsByName data generators to use
|
|
*/
|
|
/* package */ AuditApplication(
|
|
Map<String, DataExtractor> dataExtractorsByName,
|
|
Map<String, DataGenerator> dataGeneratorsByName,
|
|
Application application,
|
|
Long applicationId,
|
|
Long disabledPathsId)
|
|
{
|
|
this.dataExtractorsByName = dataExtractorsByName;
|
|
this.dataGeneratorsByName = dataGeneratorsByName;
|
|
this.application = application;
|
|
|
|
this.applicationName = application.getName();
|
|
this.applicationKey = application.getKey();
|
|
this.applicationId = applicationId;
|
|
this.disabledPathsId = disabledPathsId;
|
|
|
|
buildAuditPaths(application);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode()
|
|
{
|
|
return applicationName.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj)
|
|
{
|
|
if (this == obj)
|
|
{
|
|
return true;
|
|
}
|
|
else if (obj instanceof AuditApplication)
|
|
{
|
|
AuditApplication that = (AuditApplication) obj;
|
|
return this.applicationName.equals(that.applicationName);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
StringBuilder sb = new StringBuilder(56);
|
|
sb.append("AuditApplication")
|
|
.append("[ name=").append(applicationName)
|
|
.append(", id=").append(applicationId)
|
|
.append(", disabledPathsId=").append(disabledPathsId)
|
|
.append("]");
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Get the application name
|
|
*/
|
|
public String getApplicationName()
|
|
{
|
|
return applicationName;
|
|
}
|
|
|
|
/**
|
|
* Get the key (root path) for the application
|
|
*/
|
|
public String getApplicationKey()
|
|
{
|
|
return applicationKey;
|
|
}
|
|
|
|
/**
|
|
* Get the database ID for this application
|
|
*/
|
|
public Long getApplicationId()
|
|
{
|
|
return applicationId;
|
|
}
|
|
|
|
/**
|
|
* Get the property representing the set of disabled paths for the application
|
|
*
|
|
* @return Returns an ID <code>Set<String></code> of disabled paths
|
|
*/
|
|
public Long getDisabledPathsId()
|
|
{
|
|
return disabledPathsId;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* @see #AUDIT_PATH_REGEX
|
|
*/
|
|
public void checkPath(String path)
|
|
{
|
|
checkPathFormat(path);
|
|
if (path == null || path.length() == 0)
|
|
{
|
|
generateException(path, "Empty or null audit path");
|
|
}
|
|
else if (!path.matches(AUDIT_PATH_REGEX))
|
|
{
|
|
generateException(
|
|
path,
|
|
"An audit must match regular expression: " + AUDIT_PATH_REGEX);
|
|
}
|
|
else if (path.indexOf(applicationKey, 0) != 1)
|
|
{
|
|
generateException(
|
|
path,
|
|
"An audit path's first element must be the application's key i.e. '" + applicationKey + "'.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to check that a path is correct for this application instance
|
|
*
|
|
* @param path the path in format <b>/app-key/x/y/z</b>
|
|
* @throws AuditModelException if the path is invalid
|
|
*
|
|
* @see #AUDIT_PATH_REGEX
|
|
*/
|
|
public static void checkPathFormat(String path)
|
|
{
|
|
if (path == null || path.length() == 0)
|
|
{
|
|
throw new AuditModelException("Empty or null audit path: " + path);
|
|
}
|
|
else if (!path.matches(AUDIT_PATH_REGEX))
|
|
{
|
|
throw new AuditModelException(
|
|
"Audit path '" + path + "' does not match regular expression: " + AUDIT_PATH_REGEX);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 static String buildPath(String ... pathComponents)
|
|
{
|
|
StringBuilder sb = new StringBuilder(pathComponents.length * 10);
|
|
for (String pathComponent : pathComponents)
|
|
{
|
|
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 path;
|
|
}
|
|
|
|
/**
|
|
* @param path the audit path for form <b>/abc/def</b>
|
|
* @return the root key of form <b>abc</b>
|
|
*
|
|
* @see #AUDIT_ROOT_KEY_REGEX
|
|
*/
|
|
public static String getRootKey(String path)
|
|
{
|
|
if (!path.startsWith(AUDIT_PATH_SEPARATOR))
|
|
{
|
|
throw new AuditModelException(
|
|
"The path must start with the path separator '" + AUDIT_PATH_SEPARATOR + "'");
|
|
}
|
|
String rootPath;
|
|
int index = path.indexOf(AUDIT_PATH_SEPARATOR, 1);
|
|
if (index > 0)
|
|
{
|
|
rootPath = path.substring(1, index);
|
|
}
|
|
else
|
|
{
|
|
rootPath = path.substring(1);
|
|
}
|
|
// Done
|
|
return rootPath;
|
|
}
|
|
|
|
/**
|
|
* Utility class carrying information around a {@link DataExtractor}.
|
|
*
|
|
* @author Derek Hulley
|
|
* @since 3.4
|
|
*/
|
|
public static class DataExtractorDefinition
|
|
{
|
|
private final String dataTrigger;
|
|
private final String dataSource;
|
|
private final String dataTarget;
|
|
private final DataExtractor dataExtractor;
|
|
|
|
/**
|
|
* @param dataTrigger the data path that must exist for this extractor to be triggered
|
|
* @param dataSource the path to get data from
|
|
* @param dataTarget the path to write data to
|
|
* @param dataExtractor the implementation to use
|
|
*/
|
|
public DataExtractorDefinition(String dataTrigger, String dataSource, String dataTarget, DataExtractor dataExtractor)
|
|
{
|
|
this.dataTrigger = dataTrigger;
|
|
this.dataSource = dataSource;
|
|
this.dataTarget = dataTarget;
|
|
this.dataExtractor = dataExtractor;
|
|
}
|
|
|
|
/**
|
|
* The data path that must exist for the extractor to be triggered.
|
|
*/
|
|
public String getDataTrigger()
|
|
{
|
|
return dataTrigger;
|
|
}
|
|
|
|
public String getDataSource()
|
|
{
|
|
return dataSource;
|
|
}
|
|
|
|
public String getDataTarget()
|
|
{
|
|
return dataTarget;
|
|
}
|
|
|
|
public DataExtractor getDataExtractor()
|
|
{
|
|
return dataExtractor;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all data extractors applicable to this application.
|
|
*
|
|
* @return Returns all data extractors contained in the application
|
|
*/
|
|
public List<DataExtractorDefinition> getDataExtractors()
|
|
{
|
|
List<DataExtractorDefinition> extractors = Collections.unmodifiableList(dataExtractors);
|
|
|
|
// Done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(
|
|
"Looked up data extractors: \n" +
|
|
" Found: " + extractors);
|
|
}
|
|
return extractors;
|
|
}
|
|
|
|
/**
|
|
* Get all data generators applicable to a given path and scope.
|
|
*
|
|
* @param path the audit path
|
|
* @return Returns all data generators mapped to their key-path
|
|
*/
|
|
public Map<String, DataGenerator> getDataGenerators(String path)
|
|
{
|
|
return getDataGenerators(Collections.singleton(path));
|
|
}
|
|
|
|
/**
|
|
* Get all data generators applicable to a given path and scope.
|
|
*
|
|
* @param paths the audit paths
|
|
* @return Returns all data generators mapped to their key-path
|
|
*/
|
|
public Map<String, DataGenerator> getDataGenerators(Set<String> paths)
|
|
{
|
|
Map<String, DataGenerator> amalgamatedGenerators = new HashMap<String, DataGenerator>(13);
|
|
for (String path : paths)
|
|
{
|
|
Map<String, DataGenerator> generators = dataGenerators.get(path);
|
|
if (generators != null)
|
|
{
|
|
// Copy values to combined map
|
|
amalgamatedGenerators.putAll(generators);
|
|
}
|
|
}
|
|
|
|
// Done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(
|
|
"Looked up data generators: \n" +
|
|
" Paths: " + paths + "\n" +
|
|
" Found: " + amalgamatedGenerators);
|
|
}
|
|
return amalgamatedGenerators;
|
|
}
|
|
|
|
/**
|
|
* Internal helper method to kick off generator and extractor path mappings
|
|
*/
|
|
private void buildAuditPaths(AuditPath auditPath)
|
|
{
|
|
buildAuditPaths(
|
|
auditPath,
|
|
null,
|
|
new HashSet<String>(37),
|
|
new HashMap<String, DataGenerator>(13));
|
|
}
|
|
/**
|
|
* Recursive method to build generator and extractor mappings
|
|
*/
|
|
private void buildAuditPaths(
|
|
AuditPath auditPath,
|
|
String currentPath,
|
|
Set<String> existingPaths,
|
|
Map<String, DataGenerator> upperGeneratorsByPath)
|
|
{
|
|
// Clone the upper maps to prevent pollution
|
|
upperGeneratorsByPath = new HashMap<String, DataGenerator>(upperGeneratorsByPath);
|
|
|
|
// Append the current audit path to the current path
|
|
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.");
|
|
}
|
|
|
|
// Get the data extractors declared for this key
|
|
for (RecordValue element : auditPath.getRecordValue())
|
|
{
|
|
String key = element.getKey();
|
|
String extractorPath = AuditApplication.buildPath(currentPath, key);
|
|
if (!existingPaths.add(extractorPath))
|
|
{
|
|
generateException(extractorPath, "The audit path already exists.");
|
|
}
|
|
|
|
String extractorName = element.getDataExtractor();
|
|
DataExtractor extractor = dataExtractorsByName.get(extractorName);
|
|
if (extractor == null)
|
|
{
|
|
generateException(extractorPath, "No data extractor exists for name: " + extractorName);
|
|
}
|
|
// The extractor may pull data from somewhere else
|
|
String sourcePath = element.getDataSource();
|
|
if (sourcePath == null)
|
|
{
|
|
sourcePath = currentPath;
|
|
}
|
|
// The extractor may be triggered by data from elsewhere
|
|
String dataTrigger = element.getDataTrigger();
|
|
if (dataTrigger == null)
|
|
{
|
|
dataTrigger = currentPath;
|
|
}
|
|
// Store the extractor definition
|
|
DataExtractorDefinition extractorDef = new DataExtractorDefinition(dataTrigger, sourcePath, extractorPath, extractor);
|
|
dataExtractors.add(extractorDef);
|
|
}
|
|
|
|
// Get the data generators declared for this key
|
|
for (GenerateValue element : auditPath.getGenerateValue())
|
|
{
|
|
String key = element.getKey();
|
|
String generatorPath = AuditApplication.buildPath(currentPath, key);
|
|
if (!existingPaths.add(generatorPath))
|
|
{
|
|
generateException(generatorPath, "The audit path already exists.");
|
|
}
|
|
|
|
String generatorName = element.getDataGenerator();
|
|
DataGenerator generator = dataGeneratorsByName.get(generatorName);
|
|
if (generator == null)
|
|
{
|
|
generateException(generatorPath, "No data generator exists for name: " + generatorName);
|
|
}
|
|
// All generators that occur earlier in the path will also be applicable here
|
|
upperGeneratorsByPath.put(generatorPath, generator);
|
|
}
|
|
// All the generators apply to the current path
|
|
dataGenerators.put(currentPath, upperGeneratorsByPath);
|
|
|
|
// Find all sub audit paths and recurse
|
|
for (AuditPath element : auditPath.getAuditPath())
|
|
{
|
|
buildAuditPaths(element, currentPath, existingPaths, upperGeneratorsByPath);
|
|
}
|
|
}
|
|
|
|
private void generateException(String path, String msg) throws AuditModelException
|
|
{
|
|
throw new AuditModelException("" +
|
|
msg + "\n" +
|
|
" Application: " + applicationName + "\n" +
|
|
" Path: " + path);
|
|
}
|
|
}
|