mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
29484: Merged V3.4 to V3.4-BUG-FIX 29426: ALF-9588: Merged PATCHES/V3.4.2 to V3.4 29402: ALF-9637: Enable Share Advanced Search on properties containing a '-' in the prefix 29469: ALF-8341: Merged missing fragment from ALF-6598 29476: ALF-3061 Need to disable major/minor version radio buttons after [Upload File(s)] 29483: ALF-9407: Escape quote characters in parameterized strings 29488: Build Fix and correct fix for ALF-9632 "CMIS query fails if model name contains numbers" fix - Ensure CMIS query types are ISO9075 encoded - the user has to type them in encoded if encoding is required - the encoded look up name was in fact incorrect - not the lookup 29561: Merge DEV/BELARUS/V3.4-BUG-FIX-2011_07_13 V3.4-BUG-FIX 29422: ALF-7195 Add DisableAuditingInterceptor to NodeService and LockService. 29572: ALF-9601 - Simultaneous deployment of the same web project to two file system targets on the same engine can cause some missing files in one of the targets. 29578: Merged DEV/TEMPORARY to V3.4-BUG-FIX 29334: ALF-7390 : Alfresco ftp server never binds to a single ip address Added default configuration for bindTo property for FTP server. 29587: Merged V3.4 to V3.4-BUG-FIX 29495: Merged DEV/TEMPORARY to V3.4 29494: ALF-7701: Untranslated strings when Quickr module is installed: Task details page Global workflow messages were removed 29502: ALF-8929: Merged DEV/TEMPORARY to V3.4 29499: Rename dictionaryModelBootstrap beans for QuickR model and example model. 29504: ALF-5895: Profile CSS tweaks for other languages (Kev reviewed) 29519: Merged DEV/TEMPORARY to V3.4 (Reviewed by Kev) 29510: ALF-9419: Share - Filename changes in "Upload new version" (with italian language set) Upload WebScript was modified for support locale as a request parameter. 29518: ALF-9419: Share - Filename changes in "Upload new version" (with italian language set) ScriptUtils used for locale change. 29520: ALF-9717: Possible workaround to PolicyTest taking so long to run - Set sun.net.client.defaultConnectTimeout and sun.net.client.defaultReadTimeout System properties so that hanging request for a DTD times out quickly 29596: Merged DEV/TEMPORARY to V3.4-BUG-FIX 29595: ALF-5046: DeclarativeWebScriptRegistry causes blow-out when loading system without content 1. In org.alfresco.repo.web.scripts.RepoStore class added isContentPresent(NodeRef nodeRef) method. 2. In org.alfresco.repo.web.scripts.RepoStore#getScriptDocumentPaths added check isContentPresent(nodeRef). 3. In org.alfresco.repo.web.scripts.RepoStore#getDocumentPaths added check isContentPresent(nodeRef). 4. In org.alfresco.repo.web.scripts.RepoStore#getAllDocumentPaths added check isContentPresent(nodeRef). 29634: ALF-371 Alfresco Explorer: A change of a user's home folder now creates a new folder if it does not exist. If a home folder is shared, other users no longer find their home folder moving. However the content of the home folder must be manually moved. 29637: ALF-9847 High level audit does not include initial node properties or changed path if node moved 29685: Merged PATCHES/V3.4.1 to V3.4-BUG-FIX 29682: ALF-9777: Merged V3.4-BUG-FIX to PATCHES/V3.4.1 28188: Fix for ALF-731 - in a cluster environment (high availibility), when a node goes down, the users are asked to login 29684: ALF-9777: Correction 29686: ALF-2372 Revert action does not restore changes to document metadata Now uses VersionService.revert(...) 29697: ALF-9886: Missing space in apostrophe-laden Italian translation - The world is safe once more 29698: Merged V3.4 to V3.4-BUG-FIX 29672: Merged PATCHES/V3.4.1 to V3.4 29441: ALF-9876 / ALF-9579: Share external authentication fixes 1. Share SSO filter makes touch requests in the name of the external user ID (remoteUser) for unauthenticated users 2. Include WebScriptSSOAuthenticationFilter in external-filter-context.xml to allow cookie-based manual login failover (A Spring Surf patch is required to support this) 3. On the repository tier, web client authentication will send a 401 response for an invalid remote user (with redirect to login page in HTML markup) so that Share SSO authenticator detects this as an authentication failure and redirects to the login page, thus allowing manual login failover. 29454: ALF-9876 / ALF-9579: Share external authentication fixes 4. Spring Surf / Spring webscripts jars (from Kevin Roast) 29563: ALF-8607 / ALF-9596: Guarantee read consistency in AbstractNodeDAOImpl.getNodeRefStatus - Partly achieved by making EntityLookupCache.removeByKey() unconditionally cache removes 29673: ALF-9530: Reverse merged V3.4-BUG-FIX revisions 29124 and 29109 - Postgres service will continue to run as SYSTEM user on Windows. See bug for more details. 29674: ALF-7993: Reversed changes to bitrock files made during removal of 32 bit linux support from the build, as requested by Bitrock (V3.4-BUG-FIX revision 26582) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29700 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
789 lines
28 KiB
Java
789 lines
28 KiB
Java
/*
|
|
* Copyright (C) 2005-2011 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.access;
|
|
|
|
import static org.alfresco.repo.audit.model.AuditApplication.AUDIT_PATH_SEPARATOR;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.regex.Pattern;
|
|
|
|
import org.alfresco.repo.audit.model.AuditApplication;
|
|
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCancelCheckOut;
|
|
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCheckIn;
|
|
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCheckOut;
|
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
|
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
|
|
import org.alfresco.repo.copy.CopyServicePolicies.OnCopyCompletePolicy;
|
|
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
|
|
import org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy;
|
|
import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy;
|
|
import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy;
|
|
import org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy;
|
|
import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy;
|
|
import org.alfresco.repo.policy.PolicyScope;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.version.VersionServicePolicies.OnCreateVersionPolicy;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.namespace.InvalidQNameException;
|
|
import org.alfresco.service.namespace.NamespaceException;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
|
|
/**
|
|
* Changes made to a {@code Node} in a single transaction. For example the creation of
|
|
* a Node also involves updating properties, but the main action remains create node.
|
|
*
|
|
* @author Alan Davis
|
|
*/
|
|
/*package*/ class NodeChange implements
|
|
|
|
BeforeDeleteNodePolicy, OnAddAspectPolicy, OnCreateNodePolicy, OnMoveNodePolicy,
|
|
OnRemoveAspectPolicy, OnUpdatePropertiesPolicy,
|
|
|
|
OnContentReadPolicy, OnContentUpdatePolicy,
|
|
|
|
OnCreateVersionPolicy,
|
|
|
|
OnCopyCompletePolicy,
|
|
|
|
OnCheckOut, OnCheckIn, OnCancelCheckOut
|
|
{
|
|
private static final String USER = "user";
|
|
private static final String ACTION = "action";
|
|
private static final String SUB_ACTIONS = "sub-actions";
|
|
private static final String NODE = "node";
|
|
private static final String PATH = "path";
|
|
private static final String TYPE = "type";
|
|
private static final String FROM = "from";
|
|
private static final String TO = "to";
|
|
private static final String ADD = "add";
|
|
private static final String DELETE = "delete";
|
|
|
|
private static final String COPY = "copy";
|
|
private static final String MOVE = "move";
|
|
private static final String PROPERTIES = "properties";
|
|
private static final String ASPECTS = "aspects";
|
|
private static final String VERSION_PROPERTIES = "version-properties";
|
|
private static final String SUB_ACTION = "sub-action";
|
|
|
|
private static final String DELETE_NODE = "deleteNode";
|
|
private static final String CREATE_NODE = "createNode";
|
|
private static final String MOVE_NODE = "moveNode";
|
|
private static final String UPDATE_NODE_PROPERTIES = "updateNodeProperties";
|
|
private static final String DELETE_NODE_ASPECT = "deleteNodeAspect";
|
|
private static final String ADD_NODE_ASPECT = "addNodeAspect";
|
|
private static final String CREATE_CONTENT = "createContent";
|
|
private static final String UPDATE_CONTENT = "updateContent";
|
|
private static final String READ_CONTENT = "readContent";
|
|
private static final String CREATE_VERSION = "createVersion";
|
|
private static final String COPY_NODE = "copyNode";
|
|
private static final String CHECK_IN = "checkIn";
|
|
private static final String CHECK_OUT = "checkOut";
|
|
private static final String CANCEL_CHECK_OUT = "cancelCheckOut";
|
|
|
|
private static final String INVALID_PATH_CHAR_REPLACEMENT = "-";
|
|
private static final Pattern INVALID_PATH_COMP_CHAR_PATTERN =
|
|
Pattern.compile(AuditApplication.AUDIT_INVALID_PATH_COMP_CHAR_REGEX);
|
|
|
|
private final NodeInfoFactory nodeInfoFactory;
|
|
private final NamespaceService namespaceService;
|
|
private NodeInfo nodeInfo;
|
|
|
|
private String action;
|
|
|
|
private boolean auditSubActions = false;
|
|
private Set<String> subActions = new LinkedHashSet<String>();
|
|
private List<Map<String, Serializable>> subActionAuditMaps;
|
|
|
|
private NodeInfo copyFrom;
|
|
|
|
private NodeInfo moveFrom;
|
|
|
|
private Map<QName, Serializable> fromProperties;
|
|
private Map<QName, Serializable> toProperties;
|
|
|
|
private HashSet<QName> addedAspects;
|
|
private HashSet<QName> deletedAspects;
|
|
|
|
private HashMap<String, Serializable> versionProperties;
|
|
|
|
/*package*/ NodeChange(NodeInfoFactory nodeInfoFactory, NamespaceService namespaceService, NodeRef nodeRef)
|
|
{
|
|
this.nodeInfoFactory = nodeInfoFactory;
|
|
this.nodeInfo = nodeInfoFactory.newNodeInfo(nodeRef);
|
|
this.namespaceService = namespaceService;
|
|
}
|
|
|
|
/**
|
|
* @return a derived action for a transaction based on the sub-actions that have taken place.
|
|
*/
|
|
public String getDerivedAction()
|
|
{
|
|
// Derive higher level action
|
|
String action;
|
|
if (subActions.contains(CHECK_OUT))
|
|
{
|
|
action = "CHECK OUT";
|
|
}
|
|
else if (subActions.contains(CHECK_IN))
|
|
{
|
|
action = "CHECK IN";
|
|
}
|
|
else if (subActions.contains(CANCEL_CHECK_OUT))
|
|
{
|
|
action = "CANCEL CHECK OUT";
|
|
}
|
|
else if (subActions.contains(COPY_NODE))
|
|
{
|
|
action = "COPY";
|
|
}
|
|
else if (subActions.contains(CREATE_NODE))
|
|
{
|
|
action = "CREATE";
|
|
}
|
|
else if (subActions.size() == 1 && subActions.contains(READ_CONTENT))
|
|
{
|
|
// Reads in combinations with other actions tend to only facilitate the other action.
|
|
action = "READ";
|
|
}
|
|
else if (subActions.contains(DELETE_NODE))
|
|
{
|
|
action = "DELETE";
|
|
}
|
|
else if (subActions.contains(CREATE_VERSION)) // && !subActions.contains(CREATE_NODE)
|
|
{
|
|
action = "CREATE VERSION";
|
|
}
|
|
else if (subActions.contains(UPDATE_CONTENT)) // && !subActions.contains(CREATE_NODE)
|
|
{
|
|
action = "UPDATE CONTENT";
|
|
}
|
|
else if (subActions.contains(MOVE_NODE))
|
|
{
|
|
action = "MOVE";
|
|
}
|
|
else
|
|
{
|
|
// Default to first sub-action
|
|
action = this.action;
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
/**
|
|
* @return {@code true} if the node has been created and then deleted.
|
|
*/
|
|
public boolean isTemporaryNode()
|
|
{
|
|
// No need to check the order as a new node would be given a ned node ref.
|
|
return subActions.contains(CREATE_NODE) && subActions.contains(DELETE_NODE);
|
|
}
|
|
|
|
private NodeChange setAction(String action)
|
|
{
|
|
this.action = action;
|
|
return this;
|
|
}
|
|
|
|
private void appendSubAction(NodeChange subNodeChange)
|
|
{
|
|
// Remember sub-actions so we can check them later to derive the high level action
|
|
subActions.add(subNodeChange.action);
|
|
|
|
// Default the action to the first sub-action;
|
|
if (action == null)
|
|
{
|
|
action = subNodeChange.action;
|
|
}
|
|
|
|
// Audit sub actions if required.
|
|
if (auditSubActions)
|
|
{
|
|
if (subActionAuditMaps == null)
|
|
{
|
|
subActionAuditMaps = new ArrayList<Map<String, Serializable>>();
|
|
}
|
|
subActionAuditMaps.add(subNodeChange.getAuditData(true));
|
|
}
|
|
}
|
|
|
|
public NodeChange setAuditSubActions(boolean auditSubActions)
|
|
{
|
|
this.auditSubActions = auditSubActions;
|
|
return this;
|
|
}
|
|
|
|
private NodeChange setCopyFrom(NodeRef copyFrom)
|
|
{
|
|
this.copyFrom = nodeInfoFactory.newNodeInfo(copyFrom);
|
|
return this;
|
|
}
|
|
|
|
private NodeChange setMoveFrom(ChildAssociationRef childAssocRef)
|
|
{
|
|
// Don't overwrite original value if multiple calls.
|
|
if (moveFrom == null)
|
|
{
|
|
moveFrom = nodeInfoFactory.newNodeInfo(childAssocRef);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private NodeChange setMoveTo(ChildAssociationRef childAssocRef)
|
|
{
|
|
nodeInfo = nodeInfoFactory.newNodeInfo(childAssocRef);
|
|
|
|
// Clear values if we are back to where we started.
|
|
if (nodeInfo.equals(moveFrom))
|
|
{
|
|
moveFrom = null;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private NodeChange setFromProperties(Map<QName, Serializable> fromProperties)
|
|
{
|
|
// Don't overwrite original value if multiple calls.
|
|
if (this.fromProperties == null)
|
|
{
|
|
this.fromProperties = fromProperties;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private NodeChange setToProperties(Map<QName, Serializable> toProperties)
|
|
{
|
|
this.toProperties = toProperties;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add an aspect - if just deleted, remove the delete, otherwise record the add.
|
|
*/
|
|
private NodeChange addAspect(QName aspect)
|
|
{
|
|
if (addedAspects == null)
|
|
{
|
|
addedAspects = new HashSet<QName>();
|
|
}
|
|
|
|
// Consider sequences
|
|
// add = add
|
|
// del add = ---
|
|
// add del add = add
|
|
// add add = add
|
|
if (deletedAspects != null && deletedAspects.contains(aspect))
|
|
{
|
|
deletedAspects.remove(aspect);
|
|
}
|
|
else
|
|
{
|
|
addedAspects.add(aspect);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Delete an aspect - if just added, remove the add, otherwise record the delete.
|
|
*/
|
|
private NodeChange deleteAspect(QName aspect)
|
|
{
|
|
if (deletedAspects == null)
|
|
{
|
|
deletedAspects = new HashSet<QName>();
|
|
}
|
|
|
|
// Consider sequences
|
|
// del = del
|
|
// add del = ---
|
|
// del add del = del
|
|
// del del = del
|
|
if (addedAspects != null && addedAspects.contains(aspect))
|
|
{
|
|
addedAspects.remove(aspect);
|
|
}
|
|
else
|
|
{
|
|
deletedAspects.add(aspect);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
private NodeChange setVersionProperties(
|
|
HashMap<String, Serializable> versionProperties)
|
|
{
|
|
if (this.versionProperties == null)
|
|
{
|
|
this.versionProperties = new HashMap<String, Serializable>();
|
|
}
|
|
this.versionProperties.putAll(versionProperties);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public void beforeDeleteNode(NodeRef nodeRef)
|
|
{
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(DELETE_NODE));
|
|
}
|
|
|
|
@Override
|
|
public void onCreateNode(ChildAssociationRef childAssocRef)
|
|
{
|
|
NodeRef nodeRef = childAssocRef.getChildRef();
|
|
Map<QName, Serializable> fromProperties = Collections.<QName, Serializable>emptyMap();
|
|
Map<QName, Serializable> toProperties = nodeInfoFactory.getProperties(nodeRef);
|
|
this.fromProperties = null; // Sometimes onCreateNode policy is out of order (e.g. create a person)
|
|
setFromProperties(fromProperties);
|
|
setToProperties(toProperties);
|
|
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(CREATE_NODE).
|
|
setFromProperties(fromProperties).
|
|
setToProperties(toProperties));
|
|
}
|
|
|
|
@Override
|
|
public void onMoveNode(ChildAssociationRef fromChildAssocRef, ChildAssociationRef toChildAssocRef)
|
|
{
|
|
setMoveFrom(fromChildAssocRef);
|
|
setMoveTo(toChildAssocRef);
|
|
|
|
// Note: A change of the child node name will be picked up as a property name change.
|
|
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, toChildAssocRef.getChildRef()).
|
|
setAction(MOVE_NODE).
|
|
setMoveFrom(fromChildAssocRef).
|
|
setMoveTo(toChildAssocRef));
|
|
}
|
|
|
|
@Override
|
|
public void onUpdateProperties(NodeRef nodeRef,
|
|
Map<QName, Serializable> fromProperties, Map<QName, Serializable> toProperties)
|
|
{
|
|
// Sometime we don't get the fromProperties, so just use the latest we have
|
|
if (fromProperties.isEmpty() && this.toProperties != null)
|
|
{
|
|
fromProperties = this.toProperties;
|
|
}
|
|
|
|
setFromProperties(fromProperties);
|
|
setToProperties(toProperties);
|
|
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(UPDATE_NODE_PROPERTIES).
|
|
setFromProperties(fromProperties).
|
|
setToProperties(toProperties));
|
|
}
|
|
|
|
@Override
|
|
public void onRemoveAspect(NodeRef nodeRef, QName aspect)
|
|
{
|
|
deleteAspect(aspect);
|
|
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(DELETE_NODE_ASPECT).
|
|
deleteAspect(aspect));
|
|
}
|
|
|
|
@Override
|
|
public void onAddAspect(NodeRef nodeRef, QName aspect)
|
|
{
|
|
addAspect(aspect);
|
|
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(ADD_NODE_ASPECT).
|
|
addAspect(aspect));
|
|
}
|
|
|
|
@Override
|
|
public void onContentUpdate(NodeRef nodeRef, boolean newContent)
|
|
{
|
|
if (newContent)
|
|
{
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(CREATE_CONTENT));
|
|
}
|
|
else
|
|
{
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(UPDATE_CONTENT));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onContentRead(NodeRef nodeRef)
|
|
{
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(READ_CONTENT));
|
|
}
|
|
|
|
@Override
|
|
public void onCreateVersion(QName classRef, NodeRef nodeRef,
|
|
Map<String, Serializable> versionProperties, PolicyScope nodeDetails)
|
|
{
|
|
setVersionProperties((HashMap<String, Serializable>)versionProperties);
|
|
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(CREATE_VERSION).
|
|
setVersionProperties((HashMap<String, Serializable>)versionProperties));
|
|
// Note nodeDetails are not used
|
|
}
|
|
|
|
public void onCopyComplete(QName classRef, NodeRef sourceNodeRef, NodeRef targetNodeRef,
|
|
boolean copyToNewNode, Map<NodeRef, NodeRef> copyMap)
|
|
{
|
|
setCopyFrom(sourceNodeRef);
|
|
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, targetNodeRef).
|
|
setAction(COPY_NODE).
|
|
setCopyFrom(sourceNodeRef));
|
|
}
|
|
|
|
public void onCheckOut(NodeRef workingCopy)
|
|
{
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, workingCopy).
|
|
setAction(CHECK_OUT));
|
|
}
|
|
|
|
public void onCheckIn(NodeRef nodeRef)
|
|
{
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(CHECK_IN));
|
|
}
|
|
|
|
public void onCancelCheckOut(NodeRef nodeRef)
|
|
{
|
|
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
|
|
setAction(CANCEL_CHECK_OUT));
|
|
}
|
|
|
|
public Map<String, Serializable> getAuditData(boolean subAction)
|
|
{
|
|
Map<String, Serializable> auditMap = new HashMap<String, Serializable>(
|
|
2 *
|
|
(1 + // action
|
|
1 + // user
|
|
1 + // sub actions
|
|
3 + // node, path, type
|
|
3 + // copy source's node, path, type
|
|
3 + // move source's node, path, type
|
|
(fromProperties != null ? fromProperties.size() + toProperties.size() + 4 : 0) +
|
|
// individual property changes
|
|
// grouped from, to, add and delete changes
|
|
(addedAspects != null ? addedAspects.size() : 0) +
|
|
(deletedAspects != null ? deletedAspects.size() : 0) +
|
|
(versionProperties != null ? versionProperties.size()+1 : 0) +
|
|
getSubAuditDataSize()));
|
|
|
|
// For a transaction, set the action
|
|
if (!subAction)
|
|
{
|
|
setAction(getDerivedAction());
|
|
}
|
|
auditMap.put(ACTION, action);
|
|
|
|
if (!subAction) // no need to repeat for sub actions
|
|
{
|
|
auditMap.put(USER, AuthenticationUtil.getFullyAuthenticatedUser());
|
|
addSubActionsToAuditMap(auditMap);
|
|
|
|
auditMap.put(NODE, nodeInfo.getNodeRef());
|
|
auditMap.put(PATH, nodeInfo.getPrefixPath());
|
|
auditMap.put(TYPE, nodeInfo.getPrefixType());
|
|
}
|
|
|
|
if (copyFrom != null)
|
|
{
|
|
auditMap.put(buildPath(COPY, FROM, NODE), copyFrom.getNodeRef());
|
|
auditMap.put(buildPath(COPY, FROM, PATH), copyFrom.getPrefixPath());
|
|
auditMap.put(buildPath(COPY, FROM, TYPE), copyFrom.getPrefixType());
|
|
}
|
|
|
|
if (moveFrom != null)
|
|
{
|
|
auditMap.put(buildPath(MOVE, FROM, NODE), moveFrom.getNodeRef());
|
|
auditMap.put(buildPath(MOVE, FROM, PATH), moveFrom.getPrefixPath());
|
|
auditMap.put(buildPath(MOVE, FROM, TYPE), moveFrom.getPrefixType());
|
|
}
|
|
|
|
if (fromProperties != null)
|
|
{
|
|
addPropertyChangesToAuditMap(auditMap, subAction);
|
|
}
|
|
|
|
if (addedAspects != null && !addedAspects.isEmpty())
|
|
{
|
|
addAspectChangesToAuditMap(auditMap, ADD, addedAspects, subAction);
|
|
}
|
|
|
|
if (deletedAspects != null && !deletedAspects.isEmpty())
|
|
{
|
|
addAspectChangesToAuditMap(auditMap, DELETE, deletedAspects, subAction);
|
|
}
|
|
|
|
if (versionProperties != null && !versionProperties.isEmpty())
|
|
{
|
|
addVersionPropertiesToAuditMap(auditMap, versionProperties, subAction);
|
|
}
|
|
|
|
addSubActionAuditMapsToAuditMap(auditMap);
|
|
|
|
return auditMap;
|
|
}
|
|
|
|
private void addSubActionsToAuditMap(Map<String, Serializable> auditMap)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
for (String subAction: subActions)
|
|
{
|
|
if (sb.length() > 0)
|
|
{
|
|
sb.append(' ');
|
|
}
|
|
sb.append(subAction);
|
|
}
|
|
|
|
auditMap.put(SUB_ACTIONS, sb.toString());
|
|
}
|
|
|
|
private void addPropertyChangesToAuditMap(Map<String, Serializable> auditMap, boolean subAction)
|
|
{
|
|
HashMap<QName, Serializable> from = new HashMap<QName, Serializable>(fromProperties.size());
|
|
HashMap<QName, Serializable> to = new HashMap<QName, Serializable>(toProperties.size());
|
|
HashMap<QName, Serializable> add = new HashMap<QName, Serializable>(toProperties.size());
|
|
HashMap<QName, Serializable> delete = new HashMap<QName, Serializable>(fromProperties.size());
|
|
|
|
// Initially check for changes to existing keys and values.
|
|
// Record individual value changes and group (from, to, delete) changes in their own maps.
|
|
for (Map.Entry<QName, Serializable> entry : fromProperties.entrySet())
|
|
{
|
|
// Audit QNames with the prefix set. The key can be used in original Set and
|
|
// Map operations as only the namesapace and local name are used in equals and
|
|
// hashcode methods.
|
|
QName key = getPrefixedQName(entry.getKey());
|
|
|
|
String name = replaceInvalidPathChars(key.toPrefixString());
|
|
Serializable beforeValue = entry.getValue();
|
|
Serializable afterValue = null;
|
|
|
|
boolean exists = toProperties.containsKey(key);
|
|
boolean same = false;
|
|
if (exists)
|
|
{
|
|
// Audit nothing if both values are null or equal.
|
|
afterValue = toProperties.get(key);
|
|
if ((beforeValue == afterValue) ||
|
|
(beforeValue != null && beforeValue.equals(afterValue)))
|
|
same = true;
|
|
}
|
|
|
|
if (!same)
|
|
{
|
|
if (exists)
|
|
{
|
|
auditMap.put(buildPath(PROPERTIES, FROM, name), beforeValue);
|
|
auditMap.put(buildPath(PROPERTIES, TO, name), afterValue);
|
|
from.put(key, beforeValue);
|
|
to.put(key, afterValue);
|
|
}
|
|
else
|
|
{
|
|
auditMap.put(buildPath(PROPERTIES, DELETE, name), beforeValue);
|
|
delete.put(key, beforeValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for new values. Record individual values and group as a single map.
|
|
Set<QName> newKeys = new HashSet<QName>(toProperties.keySet());
|
|
newKeys.removeAll(fromProperties.keySet());
|
|
for (QName key: newKeys)
|
|
{
|
|
// Audit QNames with the prefix set.
|
|
key = getPrefixedQName(key);
|
|
|
|
Serializable afterValue = toProperties.get(key);
|
|
String name = replaceInvalidPathChars(key.toPrefixString());
|
|
auditMap.put(buildPath(PROPERTIES, ADD, name), afterValue);
|
|
add.put(key, afterValue);
|
|
}
|
|
|
|
// Record maps of additions, deletes and paired from and to values.
|
|
if (!subAction)
|
|
{
|
|
if (!add.isEmpty())
|
|
{
|
|
auditMap.put(buildPath(PROPERTIES, ADD), add);
|
|
}
|
|
if (!delete.isEmpty())
|
|
{
|
|
auditMap.put(buildPath(PROPERTIES, DELETE), delete);
|
|
}
|
|
if (!from.isEmpty())
|
|
{
|
|
auditMap.put(buildPath(PROPERTIES, FROM), from);
|
|
}
|
|
if (!to.isEmpty())
|
|
{
|
|
auditMap.put(buildPath(PROPERTIES, TO), to);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void addAspectChangesToAuditMap(Map<String, Serializable> auditMap,
|
|
String addOrDelete, HashSet<QName> aspects, boolean subAction)
|
|
{
|
|
// Audit Set<QName> where the QName has the prefix set.
|
|
HashSet<QName> prefixedAspects = new HashSet<QName>(aspects.size());
|
|
for (QName aspect: aspects)
|
|
{
|
|
// Audit QNames with the prefix set.
|
|
aspect = getPrefixedQName(aspect);
|
|
|
|
prefixedAspects.add(aspect);
|
|
String name = replaceInvalidPathChars(aspect.toPrefixString());
|
|
auditMap.put(buildPath(ASPECTS, addOrDelete, name), null);
|
|
}
|
|
if (!subAction)
|
|
{
|
|
auditMap.put(buildPath(ASPECTS, addOrDelete), prefixedAspects);
|
|
}
|
|
}
|
|
|
|
private void addVersionPropertiesToAuditMap(Map<String, Serializable> auditMap,
|
|
HashMap<String, Serializable> properties, boolean subAction)
|
|
{
|
|
for (Map.Entry<String, Serializable> entry: properties.entrySet())
|
|
{
|
|
// The key may be the string version ({URI}localName) of a QName.
|
|
// If so get the prefixed version.
|
|
String key = entry.getKey();
|
|
if (key.indexOf(QName.NAMESPACE_PREFIX) == 0)
|
|
{
|
|
try
|
|
{
|
|
QName qNameKey = QName.createQName(key);
|
|
qNameKey = getPrefixedQName(qNameKey);
|
|
key = qNameKey.toPrefixString();
|
|
}
|
|
catch (InvalidQNameException e)
|
|
{
|
|
// Its just a String.
|
|
}
|
|
}
|
|
|
|
String name = replaceInvalidPathChars(key);
|
|
auditMap.put(buildPath(VERSION_PROPERTIES, name), entry.getValue());
|
|
}
|
|
if (!subAction)
|
|
{
|
|
auditMap.put(VERSION_PROPERTIES, properties);
|
|
}
|
|
}
|
|
|
|
private int getSubAuditDataSize()
|
|
{
|
|
int size = 0;
|
|
// No point doing sub actions if only one!
|
|
if (subActionAuditMaps != null && subActionAuditMaps.size() > 1)
|
|
{
|
|
for (Map<String, Serializable> subActionAuditMap: subActionAuditMaps)
|
|
{
|
|
size += subActionAuditMap.size();
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
private void addSubActionAuditMapsToAuditMap(Map<String, Serializable> auditMap)
|
|
{
|
|
// No point doing sub actions if only one!
|
|
if (subActionAuditMaps != null && subActionAuditMaps.size() > 1)
|
|
{
|
|
String format = "%0"+Integer.toString(auditMap.size()).length()+"d";
|
|
int i = 0;
|
|
for (Map<String, Serializable> subActionAuditMap : subActionAuditMaps)
|
|
{
|
|
String seq = String.format(format, i);
|
|
for (Map.Entry<String, Serializable> entry : subActionAuditMap.entrySet())
|
|
{
|
|
auditMap.put(buildPath(SUB_ACTION, seq, entry.getKey()), entry.getValue());
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a path to be used in an audit map. Unlike {@link AuditApplication#buildPath(String...)}
|
|
* the returned value is relative (no leading slash).
|
|
* @param components String.. components of the path.
|
|
* @return a component path of the supplied values.
|
|
*/
|
|
private String buildPath(String... components)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
for (String component: components)
|
|
{
|
|
if (sb.length() > 0)
|
|
{
|
|
sb.append(AUDIT_PATH_SEPARATOR);
|
|
}
|
|
sb.append(component);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* @return a new QName that has the prefix set or the original if it is unknown.
|
|
*/
|
|
private QName getPrefixedQName(QName qName)
|
|
{
|
|
try
|
|
{
|
|
qName = qName.getPrefixedQName(namespaceService);
|
|
}
|
|
catch (NamespaceException e)
|
|
{
|
|
// ignore - go with what we have
|
|
}
|
|
return qName;
|
|
}
|
|
|
|
/**
|
|
* @return a String where all invalid audit path characters are replaced by a '-'.
|
|
*/
|
|
private String replaceInvalidPathChars(String path)
|
|
{
|
|
return INVALID_PATH_COMP_CHAR_PATTERN.matcher(path).replaceAll(INVALID_PATH_CHAR_REPLACEMENT);
|
|
}
|
|
} |